diff options
Diffstat (limited to 'src/com/android')
7 files changed, 2076 insertions, 408 deletions
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java index b7e410a..71e84c6 100644 --- a/src/com/android/providers/telephony/MmsProvider.java +++ b/src/com/android/providers/telephony/MmsProvider.java @@ -66,12 +66,13 @@ public class MmsProvider extends ContentProvider { static final String VIEW_PDU_RESTRICTED = "pdu_restricted"; // The name of parts directory. The full dir is "app_parts". - private static final String PARTS_DIR_NAME = "parts"; + static final String PARTS_DIR_NAME = "parts"; @Override public boolean onCreate() { setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); - mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); + mOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); + TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); return true; } @@ -552,7 +553,7 @@ public class MmsProvider extends ContentProvider { } if (notify) { - notifyChange(); + notifyChange(res); } return res; } @@ -648,7 +649,7 @@ public class MmsProvider extends ContentProvider { } if ((deletedRows > 0) && notify) { - notifyChange(); + notifyChange(uri); } return deletedRows; } @@ -824,7 +825,7 @@ public class MmsProvider extends ContentProvider { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int count = db.update(table, finalValues, finalSelection, selectionArgs); if (notify && (count > 0)) { - notifyChange(); + notifyChange(uri); } return count; } @@ -941,9 +942,11 @@ public class MmsProvider extends ContentProvider { values.remove(Mms._ID); } - private void notifyChange() { - getContext().getContentResolver().notifyChange( + private void notifyChange(final Uri uri) { + final Context context = getContext(); + context.getContentResolver().notifyChange( MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); + ProviderUtil.notifyIfNotDefaultSmsApp(uri, getCallingPackage(), context); } private final static String TAG = "MmsProvider"; diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java index e763f35..610418e 100644 --- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java +++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.os.storage.StorageManager; import android.provider.BaseColumns; import android.provider.Telephony; import android.provider.Telephony.Mms; @@ -48,6 +49,23 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +/** + * A {@link SQLiteOpenHelper} that handles DB management of SMS and MMS tables. + * + * From N, SMS and MMS tables are split into two groups with different levels of encryption. + * - the raw table, which lives inside DE(Device Encrypted) storage. + * - all other tables, which lives under CE(Credential Encrypted) storage. + * + * All tables are created by this class in the same database that can live either in DE or CE + * storage. But not all tables in the same database should be used. Only DE tables should be used + * in the database created in DE and only CE tables should be used in the database created in CE. + * The only exception is a non-FBE device migrating from M to N, in which case the DE and CE tables + * will actually live inside the same storage/database. + * + * This class provides methods to create instances that manage databases in different storage. + * It's the responsibility of the clients of this class to make sure the right instance is + * used to access tables that are supposed to live inside the intended storage. + */ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "MmsSmsDatabaseHelper"; @@ -211,12 +229,13 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { " AND part.mid = pdu._id);" + " END"; - private static MmsSmsDatabaseHelper sInstance = null; + private static MmsSmsDatabaseHelper sDeInstance = null; + private static MmsSmsDatabaseHelper sCeInstance = null; private static boolean sTriedAutoIncrement = false; private static boolean sFakeLowStorageTest = false; // for testing only static final String DATABASE_NAME = "mmssms.db"; - static final int DATABASE_VERSION = 61; + static final int DATABASE_VERSION = 64; private final Context mContext; private LowStorageMonitor mLowStorageMonitor; @@ -228,14 +247,29 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { } /** - * Return a singleton helper for the combined MMS and SMS - * database. + * Returns a singleton helper for the combined MMS and SMS database in device encrypted storage. + */ + /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForDe(Context context) { + if (sDeInstance == null) { + sDeInstance = new MmsSmsDatabaseHelper(ProviderUtil.getDeviceEncryptedContext(context)); + } + return sDeInstance; + } + + /** + * Returns a singleton helper for the combined MMS and SMS database in credential encrypted + * storage. If FBE is not available, use the device encrypted storage instead. */ - /* package */ static synchronized MmsSmsDatabaseHelper getInstance(Context context) { - if (sInstance == null) { - sInstance = new MmsSmsDatabaseHelper(context); + /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForCe(Context context) { + if (sCeInstance == null) { + if (StorageManager.isFileEncryptedNativeOrEmulated()) { + sCeInstance = new MmsSmsDatabaseHelper( + ProviderUtil.getCredentialEncryptedContext(context)); + } else { + sCeInstance = getInstanceForDe(context); + } } - return sInstance; + return sCeInstance; } /** @@ -267,15 +301,15 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { // Now build a selection string of all the unique recipient ids StringBuilder sb = new StringBuilder(); Iterator<Integer> iter = recipientIds.iterator(); + sb.append("_id NOT IN ("); while (iter.hasNext()) { - sb.append("_id != " + iter.next()); + sb.append(iter.next()); if (iter.hasNext()) { - sb.append(" AND "); + sb.append(","); } } - if (sb.length() > 0) { - int rows = db.delete("canonical_addresses", sb.toString(), null); - } + sb.append(")"); + int rows = db.delete("canonical_addresses", sb.toString(), null); } } finally { c.close(); @@ -867,7 +901,9 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { "destination_port INTEGER," + "address TEXT," + "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + - "pdu TEXT);"); // the raw PDU for this part + "pdu TEXT," + // the raw PDU for this part + "deleted INTEGER DEFAULT 0," + // bool to indicate if row is deleted + "message_body TEXT);"); // message body db.execSQL("CREATE TABLE attachments (" + "sms_id INTEGER," + @@ -1363,6 +1399,55 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { } finally { db.endTransaction(); } + // fall through + case 61: + if (currentVersion <= 61) { + return; + } + + db.beginTransaction(); + try { + upgradeDatabaseToVersion62(db); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(TAG, ex.getMessage(), ex); + break; + } finally { + db.endTransaction(); + } + // fall through + case 62: + if (currentVersion <= 62) { + return; + } + + db.beginTransaction(); + try { + upgradeDatabaseToVersion63(db); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(TAG, ex.getMessage(), ex); + break; + } finally { + db.endTransaction(); + } + // fall through + case 63: + if (currentVersion <= 63) { + return; + } + + db.beginTransaction(); + try { + upgradeDatabaseToVersion64(db); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(TAG, ex.getMessage(), ex); + break; + } finally { + db.endTransaction(); + } + return; } @@ -1607,6 +1692,44 @@ public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { } + private void upgradeDatabaseToVersion62(SQLiteDatabase db) { + // When a non-FBE device is upgraded to N, all MMS attachment files are moved from + // /data/data to /data/user_de. We need to update the paths stored in the parts table to + // reflect this change. + String newPartsDirPath; + try { + newPartsDirPath = mContext.getDir(MmsProvider.PARTS_DIR_NAME, 0).getCanonicalPath(); + } + catch (IOException e){ + Log.e(TAG, "openFile: check file path failed " + e, e); + return; + } + + // The old path of the part files will be something like this: + // /data/data/0/com.android.providers.telephony/app_parts + // The new path of the part files will be something like this: + // /data/user_de/0/com.android.providers.telephony/app_parts + int partsDirIndex = newPartsDirPath.lastIndexOf( + File.separator, newPartsDirPath.lastIndexOf(MmsProvider.PARTS_DIR_NAME)); + String partsDirName = newPartsDirPath.substring(partsDirIndex) + File.separator; + // The query to update the part path will be: + // UPDATE part SET _data = '/data/user_de/0/com.android.providers.telephony' || + // SUBSTR(_data, INSTR(_data, '/app_parts/')) + // WHERE INSTR(_data, '/app_parts/') > 0 + db.execSQL("UPDATE " + MmsProvider.TABLE_PART + + " SET " + Part._DATA + " = '" + newPartsDirPath.substring(0, partsDirIndex) + "' ||" + + " SUBSTR(" + Part._DATA + ", INSTR(" + Part._DATA + ", '" + partsDirName + "'))" + + " WHERE INSTR(" + Part._DATA + ", '" + partsDirName + "') > 0"); + } + + private void upgradeDatabaseToVersion63(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN deleted INTEGER DEFAULT 0"); + } + + private void upgradeDatabaseToVersion64(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN message_body TEXT"); + } + @Override public synchronized SQLiteDatabase getWritableDatabase() { SQLiteDatabase db = super.getWritableDatabase(); diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java index 0e4e447..d5e0ef9 100644 --- a/src/com/android/providers/telephony/MmsSmsProvider.java +++ b/src/com/android/providers/telephony/MmsSmsProvider.java @@ -308,10 +308,11 @@ public class MmsSmsProvider extends ContentProvider { @Override public boolean onCreate() { setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); - mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); + mOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); mUseStrictPhoneNumberComparation = getContext().getResources().getBoolean( com.android.internal.R.bool.config_use_strict_phone_number_comparation); + TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); return true; } diff --git a/src/com/android/providers/telephony/ProviderUtil.java b/src/com/android/providers/telephony/ProviderUtil.java index 9435409..4234b06 100644 --- a/src/com/android/providers/telephony/ProviderUtil.java +++ b/src/com/android/providers/telephony/ProviderUtil.java @@ -16,10 +16,15 @@ package com.android.providers.telephony; +import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.os.Process; import android.provider.Telephony; +import android.text.TextUtils; +import android.util.Log; import com.android.internal.telephony.SmsApplication; @@ -27,6 +32,7 @@ import com.android.internal.telephony.SmsApplication; * Helpers */ public class ProviderUtil { + private final static String TAG = "SmsProvider"; /** * Check if a caller of the provider has restricted access, @@ -68,4 +74,55 @@ public class ProviderUtil { (values.containsKey(Telephony.Sms.CREATOR) || values.containsKey(Telephony.Mms.CREATOR)); } + + /** + * Notify the default SMS app of an SMS/MMS provider change if the change is being made + * by a package other than the default SMS app itself. + * + * @param uri The uri the provider change applies to + * @param callingPackage The package name of the provider caller + * @param Context + */ + public static void notifyIfNotDefaultSmsApp(final Uri uri, final String callingPackage, + final Context context) { + if (TextUtils.equals(callingPackage, Telephony.Sms.getDefaultSmsPackage(context))) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "notifyIfNotDefaultSmsApp - called from default sms app"); + } + return; + } + // Direct the intent to only the default SMS app, and only if the SMS app has a receiver + // for the intent. + ComponentName componentName = + SmsApplication.getDefaultExternalTelephonyProviderChangedApplication(context, true); + if (componentName == null) { + return; // the default sms app doesn't have a receiver for this intent + } + + final Intent intent = + new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE); + intent.setFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.setComponent(componentName); + if (uri != null) { + intent.setData(uri); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "notifyIfNotDefaultSmsApp - called from " + callingPackage + ", notifying"); + } + context.sendBroadcast(intent); + } + + public static Context getCredentialEncryptedContext(Context context) { + if (context.isCredentialProtectedStorage()) { + return context; + } + return context.createCredentialProtectedStorageContext(); + } + + public static Context getDeviceEncryptedContext(Context context) { + if (context.isDeviceProtectedStorage()) { + return context; + } + return context.createDeviceProtectedStorageContext(); + } } diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java index d48f1c6..f50f804 100644 --- a/src/com/android/providers/telephony/SmsProvider.java +++ b/src/com/android/providers/telephony/SmsProvider.java @@ -16,10 +16,12 @@ package com.android.providers.telephony; +import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.DatabaseUtils; @@ -59,6 +61,9 @@ public class SmsProvider extends ContentProvider { new String[] { Contacts.Phones.PERSON_ID }; private static final int PERSON_ID_COLUMN = 0; + /** Delete any raw messages or message segments marked deleted that are older than an hour. */ + static final long RAW_MESSAGE_EXPIRE_AGE_MS = (long) (60 * 60 * 1000); + /** * These are the columns that are available when reading SMS * messages from the ICC. Columns whose names begin with "is_" @@ -85,7 +90,9 @@ public class SmsProvider extends ContentProvider { @Override public boolean onCreate() { setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS); - mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext()); + mDeOpenHelper = MmsSmsDatabaseHelper.getInstanceForDe(getContext()); + mCeOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext()); + TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext()); return true; } @@ -113,6 +120,7 @@ public class SmsProvider extends ContentProvider { // Generate the body of the query. int match = sURLMatcher.match(url); + SQLiteDatabase db = getDBOpenHelper(match).getReadableDatabase(); switch (match) { case SMS_ALL: constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable); @@ -201,6 +209,8 @@ public class SmsProvider extends ContentProvider { break; case SMS_RAW_MESSAGE: + // before querying purge old entries with deleted = 1 + purgeDeletedMessagesInRawTable(db); qb.setTables("raw"); break; @@ -251,7 +261,6 @@ public class SmsProvider extends ContentProvider { orderBy = Sms.DEFAULT_SORT_ORDER; } - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, orderBy); @@ -261,6 +270,22 @@ public class SmsProvider extends ContentProvider { return ret; } + private void purgeDeletedMessagesInRawTable(SQLiteDatabase db) { + long oldTimestamp = System.currentTimeMillis() - RAW_MESSAGE_EXPIRE_AGE_MS; + int num = db.delete(TABLE_RAW, "deleted = 1 AND date < " + oldTimestamp, null); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "purgeDeletedMessagesInRawTable: num rows older than " + oldTimestamp + + " purged: " + num); + } + } + + private SQLiteOpenHelper getDBOpenHelper(int match) { + if (match == SMS_RAW_MESSAGE) { + return mDeOpenHelper; + } + return mCeOpenHelper; + } + private Object[] convertIccToSms(SmsMessage message, int id) { // N.B.: These calls must appear in the same order as the // columns appear in ICC_COLUMNS. @@ -384,12 +409,44 @@ public class SmsProvider extends ContentProvider { } @Override + public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values) { + final int callerUid = Binder.getCallingUid(); + final String callerPkg = getCallingPackage(); + long token = Binder.clearCallingIdentity(); + try { + int messagesInserted = 0; + for (ContentValues initialValues : values) { + Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg); + if (insertUri != null) { + messagesInserted++; + } + } + + // The raw table is used by the telephony layer for storing an sms before + // sending out a notification that an sms has arrived. We don't want to notify + // the default sms app of changes to this table. + final boolean notifyIfNotDefault = sURLMatcher.match(url) != SMS_RAW_MESSAGE; + notifyChange(notifyIfNotDefault, url, callerPkg); + return messagesInserted; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public Uri insert(Uri url, ContentValues initialValues) { final int callerUid = Binder.getCallingUid(); final String callerPkg = getCallingPackage(); long token = Binder.clearCallingIdentity(); try { - return insertInner(url, initialValues, callerUid, callerPkg); + Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg); + + // The raw table is used by the telephony layer for storing an sms before + // sending out a notification that an sms has arrived. We don't want to notify + // the default sms app of changes to this table. + final boolean notifyIfNotDefault = sURLMatcher.match(url) != SMS_RAW_MESSAGE; + notifyChange(notifyIfNotDefault, insertUri, callerPkg); + return insertUri; } finally { Binder.restoreCallingIdentity(token); } @@ -402,6 +459,7 @@ public class SmsProvider extends ContentProvider { int match = sURLMatcher.match(url); String table = TABLE_SMS; + boolean notifyIfNotDefault = true; switch (match) { case SMS_ALL: @@ -440,6 +498,10 @@ public class SmsProvider extends ContentProvider { case SMS_RAW_MESSAGE: table = "raw"; + // The raw table is used by the telephony layer for storing an sms before + // sending out a notification that an sms has arrived. We don't want to notify + // the default sms app of changes to this table. + notifyIfNotDefault = false; break; case SMS_STATUS_PENDING: @@ -459,7 +521,7 @@ public class SmsProvider extends ContentProvider { return null; } - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase(); if (table.equals(TABLE_SMS)) { boolean addDate = false; @@ -575,10 +637,9 @@ public class SmsProvider extends ContentProvider { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "insert " + uri + " succeeded"); } - notifyChange(uri); return uri; } else { - Log.e(TAG,"insert: failed!"); + Log.e(TAG, "insert: failed!"); } return null; @@ -588,7 +649,8 @@ public class SmsProvider extends ContentProvider { public int delete(Uri url, String where, String[] whereArgs) { int count; int match = sURLMatcher.match(url); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase(); + boolean notifyIfNotDefault = true; switch (match) { case SMS_ALL: count = db.delete(TABLE_SMS, where, whereArgs); @@ -626,7 +688,21 @@ public class SmsProvider extends ContentProvider { break; case SMS_RAW_MESSAGE: - count = db.delete("raw", where, whereArgs); + ContentValues cv = new ContentValues(); + cv.put("deleted", 1); + count = db.update(TABLE_RAW, cv, where, whereArgs); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "delete: num rows marked deleted in raw table: " + count); + } + notifyIfNotDefault = false; + break; + + case SMS_RAW_MESSAGE_PERMANENT_DELETE: + count = db.delete(TABLE_RAW, where, whereArgs); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "delete: num rows permanently deleted in raw table: " + count); + } + notifyIfNotDefault = false; break; case SMS_STATUS_PENDING: @@ -643,7 +719,7 @@ public class SmsProvider extends ContentProvider { } if (count > 0) { - notifyChange(url); + notifyChange(notifyIfNotDefault, url, getCallingPackage()); } return count; } @@ -678,11 +754,14 @@ public class SmsProvider extends ContentProvider { int count = 0; String table = TABLE_SMS; String extraWhere = null; - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + boolean notifyIfNotDefault = true; + int match = sURLMatcher.match(url); + SQLiteDatabase db = getDBOpenHelper(match).getWritableDatabase(); - switch (sURLMatcher.match(url)) { + switch (match) { case SMS_RAW_MESSAGE: table = TABLE_RAW; + notifyIfNotDefault = false; break; case SMS_STATUS_PENDING: @@ -747,20 +826,27 @@ public class SmsProvider extends ContentProvider { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "update " + url + " succeeded"); } - notifyChange(url); + notifyChange(notifyIfNotDefault, url, callerPkg); } return count; } - private void notifyChange(Uri uri) { - ContentResolver cr = getContext().getContentResolver(); + private void notifyChange(boolean notifyIfNotDefault, Uri uri, final String callingPackage) { + final Context context = getContext(); + ContentResolver cr = context.getContentResolver(); cr.notifyChange(uri, null, true, UserHandle.USER_ALL); cr.notifyChange(MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL); cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null, true, UserHandle.USER_ALL); + if (notifyIfNotDefault) { + ProviderUtil.notifyIfNotDefaultSmsApp(uri, callingPackage, context); + } } - private SQLiteOpenHelper mOpenHelper; + // Db open helper for tables stored in CE(Credential Encrypted) storage. + private SQLiteOpenHelper mCeOpenHelper; + // Db open helper for tables stored in DE(Device Encrypted) storage. + private SQLiteOpenHelper mDeOpenHelper; private final static String TAG = "SmsProvider"; private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms"; @@ -796,6 +882,7 @@ public class SmsProvider extends ContentProvider { private static final int SMS_FAILED_ID = 25; private static final int SMS_QUEUED = 26; private static final int SMS_UNDELIVERED = 27; + private static final int SMS_RAW_MESSAGE_PERMANENT_DELETE = 28; private static final UriMatcher sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH); @@ -818,6 +905,7 @@ public class SmsProvider extends ContentProvider { sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS); sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID); sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE); + sURLMatcher.addURI("sms", "raw/permanentDelete", SMS_RAW_MESSAGE_PERMANENT_DELETE); sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT); sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID); sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID); diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java new file mode 100644 index 0000000..7a9d701 --- /dev/null +++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java @@ -0,0 +1,1309 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.providers.telephony; + +import com.google.android.mms.ContentType; +import com.google.android.mms.pdu.CharacterSets; + +import com.android.internal.annotations.VisibleForTesting; + +import android.annotation.TargetApi; +import android.app.AlarmManager; +import android.app.IntentService; +import android.app.backup.BackupAgent; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.app.backup.FullBackupDataOutput; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.provider.BaseColumns; +import android.provider.Telephony; +import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.JsonReader; +import android.util.JsonWriter; +import android.util.Log; +import android.util.SparseArray; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +/*** + * Backup agent for backup and restore SMS's and text MMS's. + * + * This backup agent stores SMS's into "sms_backup" file as a JSON array. Example below. + * [{"self_phone":"+1234567891011","address":"+1234567891012","body":"Example sms", + * "date":"1450893518140","date_sent":"1450893514000","status":"-1","type":"1"}, + * {"self_phone":"+1234567891011","address":"12345","body":"Example 2","date":"1451328022316", + * "date_sent":"1451328018000","status":"-1","type":"1"}] + * + * Text MMS's are stored into "mms_backup" file as a JSON array. Example below. + * [{"self_phone":"+1234567891011","date":"1451322716","date_sent":"0","m_type":"128","v":"18", + * "msg_box":"2","mms_addresses":[{"type":137,"address":"+1234567891011","charset":106}, + * {"type":151,"address":"example@example.com","charset":106}],"mms_body":"Mms to email", + * "mms_charset":106}, + * {"self_phone":"+1234567891011","sub":"MMS subject","date":"1451322955","date_sent":"0", + * "m_type":"132","v":"17","msg_box":"1","ct_l":"http://promms/servlets/NOK5BBqgUHAqugrQNM", + * "mms_addresses":[{"type":151,"address":"+1234567891011","charset":106}], + * "mms_body":"Mms\nBody\r\n", + * "mms_charset":106,"sub_cs":"106"}] + * + * It deflates the files on the flight. + * Every 1000 messages it backs up file, deletes it and creates a new one with the same name. + * + * It stores how many bytes we are over the quota and don't backup the oldest messages. + */ + +@TargetApi(Build.VERSION_CODES.M) +public class TelephonyBackupAgent extends BackupAgent { + private static final String TAG = "TelephonyBackupAgent"; + private static final boolean DEBUG = false; + + + // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + private static final int DEFAULT_DURATION = 5000; //ms + + // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + @VisibleForTesting + static final String sSmilTextOnly = + "<smil>" + + "<head>" + + "<layout>" + + "<root-layout/>" + + "<region id=\"Text\" top=\"0\" left=\"0\" " + + "height=\"100%%\" width=\"100%%\"/>" + + "</layout>" + + "</head>" + + "<body>" + + "%s" + // constructed body goes here + "</body>" + + "</smil>"; + + // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + @VisibleForTesting + static final String sSmilTextPart = + "<par dur=\"" + DEFAULT_DURATION + "ms\">" + + "<text src=\"%s\" region=\"Text\" />" + + "</par>"; + + + // JSON key for phone number a message was sent from or received to. + private static final String SELF_PHONE_KEY = "self_phone"; + // JSON key for list of addresses of MMS message. + private static final String MMS_ADDRESSES_KEY = "mms_addresses"; + // JSON key for list of recipients of the message. + private static final String RECIPIENTS = "recipients"; + // JSON key for MMS body. + private static final String MMS_BODY_KEY = "mms_body"; + // JSON key for MMS charset. + private static final String MMS_BODY_CHARSET_KEY = "mms_charset"; + + // File names suffixes for backup/restore. + private static final String SMS_BACKUP_FILE_SUFFIX = "_sms_backup"; + private static final String MMS_BACKUP_FILE_SUFFIX = "_mms_backup"; + + // File name formats for backup. It looks like 000000_sms_backup, 000001_sms_backup, etc. + private static final String SMS_BACKUP_FILE_FORMAT = "%06d"+SMS_BACKUP_FILE_SUFFIX; + private static final String MMS_BACKUP_FILE_FORMAT = "%06d"+MMS_BACKUP_FILE_SUFFIX; + + // Charset being used for reading/writing backup files. + private static final String CHARSET_UTF8 = "UTF-8"; + + // Order by ID entries from database. + private static final String ORDER_BY_ID = BaseColumns._ID + " ASC"; + + // Order by Date entries from database. We start backup from the oldest. + private static final String ORDER_BY_DATE = "date ASC"; + + // This is a hard coded string rather than a localized one because we don't want it to + // change when you change locale. + @VisibleForTesting + static final String UNKNOWN_SENDER = "\u02BCUNKNOWN_SENDER!\u02BC"; + + // Thread id for UNKNOWN_SENDER. + private long mUnknownSenderThreadId; + + // Columns from SMS database for backup/restore. + @VisibleForTesting + static final String[] SMS_PROJECTION = new String[] { + Telephony.Sms._ID, + Telephony.Sms.SUBSCRIPTION_ID, + Telephony.Sms.ADDRESS, + Telephony.Sms.BODY, + Telephony.Sms.SUBJECT, + Telephony.Sms.DATE, + Telephony.Sms.DATE_SENT, + Telephony.Sms.STATUS, + Telephony.Sms.TYPE, + Telephony.Sms.THREAD_ID + }; + + // Columns to fetch recepients of SMS. + private static final String[] SMS_RECIPIENTS_PROJECTION = { + Telephony.Threads._ID, + Telephony.Threads.RECIPIENT_IDS + }; + + // Columns from MMS database for backup/restore. + @VisibleForTesting + static final String[] MMS_PROJECTION = new String[] { + Telephony.Mms._ID, + Telephony.Mms.SUBSCRIPTION_ID, + Telephony.Mms.SUBJECT, + Telephony.Mms.SUBJECT_CHARSET, + Telephony.Mms.DATE, + Telephony.Mms.DATE_SENT, + Telephony.Mms.MESSAGE_TYPE, + Telephony.Mms.MMS_VERSION, + Telephony.Mms.MESSAGE_BOX, + Telephony.Mms.CONTENT_LOCATION, + Telephony.Mms.THREAD_ID, + Telephony.Mms.TRANSACTION_ID + }; + + // Columns from addr database for backup/restore. This database is used for fetching addresses + // for MMS message. + @VisibleForTesting + static final String[] MMS_ADDR_PROJECTION = new String[] { + Telephony.Mms.Addr.TYPE, + Telephony.Mms.Addr.ADDRESS, + Telephony.Mms.Addr.CHARSET + }; + + // Columns from part database for backup/restore. This database is used for fetching body text + // and charset for MMS message. + @VisibleForTesting + static final String[] MMS_TEXT_PROJECTION = new String[] { + Telephony.Mms.Part.TEXT, + Telephony.Mms.Part.CHARSET + }; + static final int MMS_TEXT_IDX = 0; + static final int MMS_TEXT_CHARSET_IDX = 1; + + // Buffer size for Json writer. + public static final int WRITER_BUFFER_SIZE = 32*1024; //32Kb + + // We increase how many bytes backup size over quota by 10%, so we will fit into quota on next + // backup + public static final double BYTES_OVER_QUOTA_MULTIPLIER = 1.1; + + // Maximum messages for one backup file. After reaching the limit the agent backs up the file, + // deletes it and creates a new one with the same name. + // Not final for the testing. + @VisibleForTesting + int mMaxMsgPerFile = 1000; + + // Default values for SMS, MMS, Addresses restore. + private static ContentValues sDefaultValuesSms = new ContentValues(5); + private static ContentValues sDefaultValuesMms = new ContentValues(6); + private static final ContentValues sDefaultValuesAddr = new ContentValues(2); + + // Shared preferences for the backup agent. + private static final String BACKUP_PREFS = "backup_shared_prefs"; + // Key for storing quota bytes. + private static final String QUOTA_BYTES = "backup_quota_bytes"; + // Key for storing backup data size. + private static final String BACKUP_DATA_BYTES = "backup_data_bytes"; + // Key for storing timestamp when backup agent resets quota. It does that to get onQuotaExceeded + // call so it could get the new quota if it changed. + private static final String QUOTA_RESET_TIME = "reset_quota_time"; + private static final long QUOTA_RESET_INTERVAL = 30 * AlarmManager.INTERVAL_DAY; // 30 days. + + + static { + // Consider restored messages read and seen. + sDefaultValuesSms.put(Telephony.Sms.READ, 1); + sDefaultValuesSms.put(Telephony.Sms.SEEN, 1); + sDefaultValuesSms.put(Telephony.Sms.ADDRESS, UNKNOWN_SENDER); + // If there is no sub_id with self phone number on restore set it to -1. + sDefaultValuesSms.put(Telephony.Sms.SUBSCRIPTION_ID, -1); + + sDefaultValuesMms.put(Telephony.Mms.READ, 1); + sDefaultValuesMms.put(Telephony.Mms.SEEN, 1); + sDefaultValuesMms.put(Telephony.Mms.SUBSCRIPTION_ID, -1); + sDefaultValuesMms.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_ALL); + sDefaultValuesMms.put(Telephony.Mms.TEXT_ONLY, 1); + + sDefaultValuesAddr.put(Telephony.Mms.Addr.TYPE, 0); + sDefaultValuesAddr.put(Telephony.Mms.Addr.CHARSET, CharacterSets.DEFAULT_CHARSET); + } + + + private SparseArray<String> mSubId2phone = new SparseArray<String>(); + private Map<String, Integer> mPhone2subId = new ArrayMap<String, Integer>(); + private Map<Long, Boolean> mThreadArchived = new HashMap<>(); + + private ContentResolver mContentResolver; + // How many bytes we can backup to fit into quota. + private long mBytesOverQuota; + + // Cache list of recipients by threadId. It reduces db requests heavily. Used during backup. + @VisibleForTesting + Map<Long, List<String>> mCacheRecipientsByThread = null; + // Cache threadId by list of recipients. Used during restore. + @VisibleForTesting + Map<Set<String>, Long> mCacheGetOrCreateThreadId = null; + + @Override + public void onCreate() { + super.onCreate(); + + final SubscriptionManager subscriptionManager = SubscriptionManager.from(this); + if (subscriptionManager != null) { + final List<SubscriptionInfo> subInfo = + subscriptionManager.getActiveSubscriptionInfoList(); + if (subInfo != null) { + for (SubscriptionInfo sub : subInfo) { + final String phoneNumber = getNormalizedNumber(sub); + mSubId2phone.append(sub.getSubscriptionId(), phoneNumber); + mPhone2subId.put(phoneNumber, sub.getSubscriptionId()); + } + } + } + mContentResolver = getContentResolver(); + initUnknownSender(); + } + + @VisibleForTesting + void setContentResolver(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + @VisibleForTesting + void setSubId(SparseArray<String> subId2Phone, Map<String, Integer> phone2subId) { + mSubId2phone = subId2Phone; + mPhone2subId = phone2subId; + } + + @VisibleForTesting + void initUnknownSender() { + mUnknownSenderThreadId = getOrCreateThreadId(null); + sDefaultValuesSms.put(Telephony.Sms.THREAD_ID, mUnknownSenderThreadId); + sDefaultValuesMms.put(Telephony.Mms.THREAD_ID, mUnknownSenderThreadId); + } + + @Override + public void onFullBackup(FullBackupDataOutput data) throws IOException { + SharedPreferences sharedPreferences = getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE); + if (sharedPreferences.getLong(QUOTA_RESET_TIME, Long.MAX_VALUE) < + System.currentTimeMillis()) { + clearSharedPreferences(); + } + + mBytesOverQuota = sharedPreferences.getLong(BACKUP_DATA_BYTES, 0) - + sharedPreferences.getLong(QUOTA_BYTES, Long.MAX_VALUE); + if (mBytesOverQuota > 0) { + mBytesOverQuota *= BYTES_OVER_QUOTA_MULTIPLIER; + } + + try ( + Cursor smsCursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, SMS_PROJECTION, + null, null, ORDER_BY_DATE); + // Do not backup non text-only MMS's. + Cursor mmsCursor = mContentResolver.query(Telephony.Mms.CONTENT_URI, MMS_PROJECTION, + Telephony.Mms.TEXT_ONLY+"=1", null, ORDER_BY_DATE)) { + + if (smsCursor != null) { + smsCursor.moveToFirst(); + } + if (mmsCursor != null) { + mmsCursor.moveToFirst(); + } + + // It backs up messages from the oldest to newest. First it looks at the timestamp of + // the next SMS messages and MMS message. If the SMS is older it backs up 1000 SMS + // messages, otherwise 1000 MMS messages. Repeat until out of SMS's or MMS's. + // It ensures backups are incremental. + int fileNum = 0; + while (smsCursor != null && !smsCursor.isAfterLast() && + mmsCursor != null && !mmsCursor.isAfterLast()) { + final long smsDate = TimeUnit.MILLISECONDS.toSeconds(getMessageDate(smsCursor)); + final long mmsDate = getMessageDate(mmsCursor); + if (smsDate < mmsDate) { + backupAll(data, smsCursor, + String.format(Locale.US, SMS_BACKUP_FILE_FORMAT, fileNum++)); + } else { + backupAll(data, mmsCursor, String.format(Locale.US, + MMS_BACKUP_FILE_FORMAT, fileNum++)); + } + } + + while (smsCursor != null && !smsCursor.isAfterLast()) { + backupAll(data, smsCursor, + String.format(Locale.US, SMS_BACKUP_FILE_FORMAT, fileNum++)); + } + + while (mmsCursor != null && !mmsCursor.isAfterLast()) { + backupAll(data, mmsCursor, + String.format(Locale.US, MMS_BACKUP_FILE_FORMAT, fileNum++)); + } + } + + mThreadArchived = new HashMap<>(); + } + + @VisibleForTesting + void clearSharedPreferences() { + getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE).edit() + .remove(BACKUP_DATA_BYTES) + .remove(QUOTA_BYTES) + .remove(QUOTA_RESET_TIME) + .apply(); + } + + private static long getMessageDate(Cursor cursor) { + return cursor.getLong(cursor.getColumnIndex(Telephony.Sms.DATE)); + } + + @Override + public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { + SharedPreferences sharedPreferences = getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE); + if (sharedPreferences.contains(BACKUP_DATA_BYTES) + && sharedPreferences.contains(QUOTA_BYTES)) { + // Increase backup size by the size we skipped during previous backup. + backupDataBytes += (sharedPreferences.getLong(BACKUP_DATA_BYTES, 0) + - sharedPreferences.getLong(QUOTA_BYTES, 0)) * BYTES_OVER_QUOTA_MULTIPLIER; + } + sharedPreferences.edit() + .putLong(BACKUP_DATA_BYTES, backupDataBytes) + .putLong(QUOTA_BYTES, quotaBytes) + .putLong(QUOTA_RESET_TIME, System.currentTimeMillis() + QUOTA_RESET_INTERVAL) + .apply(); + } + + private void backupAll(FullBackupDataOutput data, Cursor cursor, String fileName) + throws IOException { + if (cursor == null || cursor.isAfterLast()) { + return; + } + + int messagesWritten = 0; + try (JsonWriter jsonWriter = getJsonWriter(fileName)) { + if (fileName.endsWith(SMS_BACKUP_FILE_SUFFIX)) { + messagesWritten = putSmsMessagesToJson(cursor, jsonWriter); + } else { + messagesWritten = putMmsMessagesToJson(cursor, jsonWriter); + } + } + backupFile(messagesWritten, fileName, data); + } + + @VisibleForTesting + int putMmsMessagesToJson(Cursor cursor, + JsonWriter jsonWriter) throws IOException { + jsonWriter.beginArray(); + int msgCount; + for (msgCount = 0; msgCount < mMaxMsgPerFile && !cursor.isAfterLast(); + cursor.moveToNext()) { + msgCount += writeMmsToWriter(jsonWriter, cursor); + } + jsonWriter.endArray(); + return msgCount; + } + + @VisibleForTesting + int putSmsMessagesToJson(Cursor cursor, JsonWriter jsonWriter) throws IOException { + + jsonWriter.beginArray(); + int msgCount; + for (msgCount = 0; msgCount < mMaxMsgPerFile && !cursor.isAfterLast(); + ++msgCount, cursor.moveToNext()) { + writeSmsToWriter(jsonWriter, cursor); + } + jsonWriter.endArray(); + return msgCount; + } + + private void backupFile(int messagesWritten, String fileName, FullBackupDataOutput data) + throws IOException { + final File file = new File(getFilesDir().getPath() + "/" + fileName); + try { + if (messagesWritten > 0) { + if (mBytesOverQuota > 0) { + mBytesOverQuota -= file.length(); + return; + } + super.fullBackupFile(file, data); + } + } finally { + file.delete(); + } + } + + public static class DeferredSmsMmsRestoreService extends IntentService { + private static final String TAG = "DeferredSmsMmsRestoreService"; + + private final Comparator<File> mFileComparator = new Comparator<File>() { + @Override + public int compare(File lhs, File rhs) { + return rhs.getName().compareTo(lhs.getName()); + } + }; + + public DeferredSmsMmsRestoreService() { + super(TAG); + setIntentRedelivery(true); + } + + private TelephonyBackupAgent mTelephonyBackupAgent; + private PowerManager.WakeLock mWakeLock; + + @Override + protected void onHandleIntent(Intent intent) { + try { + mWakeLock.acquire(); + File[] files = getFilesToRestore(this); + + if (files == null || files.length == 0) { + return; + } + Arrays.sort(files, mFileComparator); + + for (File file : files) { + final String fileName = file.getName(); + try (FileInputStream fileInputStream = new FileInputStream(file)) { + mTelephonyBackupAgent.doRestoreFile(fileName, fileInputStream.getFD()); + } catch (Exception e) { + // Either IOException or RuntimeException. + Log.e(TAG, e.toString()); + } finally { + file.delete(); + } + } + } finally { + mWakeLock.release(); + } + } + + @Override + public void onCreate() { + super.onCreate(); + mTelephonyBackupAgent = new TelephonyBackupAgent(); + mTelephonyBackupAgent.attach(this); + mTelephonyBackupAgent.onCreate(); + + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + } + + @Override + public void onDestroy() { + if (mTelephonyBackupAgent != null) { + mTelephonyBackupAgent.onDestroy(); + mTelephonyBackupAgent = null; + } + super.onDestroy(); + } + + static void startIfFilesExist(Context context) { + File[] files = getFilesToRestore(context); + if (files == null || files.length == 0) { + return; + } + context.startService(new Intent(context, DeferredSmsMmsRestoreService.class)); + } + + private static File[] getFilesToRestore(Context context) { + return context.getFilesDir().listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.getName().endsWith(SMS_BACKUP_FILE_SUFFIX) || + file.getName().endsWith(MMS_BACKUP_FILE_SUFFIX); + } + }); + } + } + + @Override + public void onRestoreFinished() { + super.onRestoreFinished(); + DeferredSmsMmsRestoreService.startIfFilesExist(this); + } + + private void doRestoreFile(String fileName, FileDescriptor fd) throws IOException { + if (DEBUG) { + Log.i(TAG, "Restoring file " + fileName); + } + + try (JsonReader jsonReader = getJsonReader(fd)) { + if (fileName.endsWith(SMS_BACKUP_FILE_SUFFIX)) { + if (DEBUG) { + Log.i(TAG, "Restoring SMS"); + } + putSmsMessagesToProvider(jsonReader); + } else if (fileName.endsWith(MMS_BACKUP_FILE_SUFFIX)) { + if (DEBUG) { + Log.i(TAG, "Restoring text MMS"); + } + putMmsMessagesToProvider(jsonReader); + } else { + if (DEBUG) { + Log.e(TAG, "Unknown file to restore:" + fileName); + } + } + } + } + + @VisibleForTesting + void putSmsMessagesToProvider(JsonReader jsonReader) throws IOException { + jsonReader.beginArray(); + int msgCount = 0; + final int bulkInsertSize = mMaxMsgPerFile; + ContentValues[] values = new ContentValues[bulkInsertSize]; + while (jsonReader.hasNext()) { + ContentValues cv = readSmsValuesFromReader(jsonReader); + if (doesSmsExist(cv)) { + continue; + } + values[(msgCount++) % bulkInsertSize] = cv; + if (msgCount % bulkInsertSize == 0) { + mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, values); + } + } + if (msgCount % bulkInsertSize > 0) { + mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, + Arrays.copyOf(values, msgCount % bulkInsertSize)); + } + jsonReader.endArray(); + } + + @VisibleForTesting + void putMmsMessagesToProvider(JsonReader jsonReader) throws IOException { + jsonReader.beginArray(); + while (jsonReader.hasNext()) { + final Mms mms = readMmsFromReader(jsonReader); + if (doesMmsExist(mms)) { + if (DEBUG) { + Log.e(TAG, String.format("Mms: %s already exists", mms.toString())); + } + continue; + } + addMmsMessage(mms); + } + } + + @VisibleForTesting + static final String[] PROJECTION_ID = {BaseColumns._ID}; + private static final int ID_IDX = 0; + + private boolean doesSmsExist(ContentValues smsValues) { + final String where = String.format(Locale.US, "%s = %d and %s = %s", + Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE), + Telephony.Sms.BODY, + DatabaseUtils.sqlEscapeString(smsValues.getAsString(Telephony.Sms.BODY))); + try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, PROJECTION_ID, where, + null, null)) { + return cursor != null && cursor.getCount() > 0; + } + } + + private boolean doesMmsExist(Mms mms) { + final String where = String.format(Locale.US, "%s = %d", + Telephony.Sms.DATE, mms.values.getAsLong(Telephony.Mms.DATE)); + try (Cursor cursor = mContentResolver.query(Telephony.Mms.CONTENT_URI, PROJECTION_ID, where, + null, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + final int mmsId = cursor.getInt(ID_IDX); + final MmsBody body = getMmsBody(mmsId); + if (body != null && body.equals(mms.body)) { + return true; + } + } while (cursor.moveToNext()); + } + } + return false; + } + + private static String getNormalizedNumber(SubscriptionInfo subscriptionInfo) { + if (subscriptionInfo == null) { + return null; + } + return PhoneNumberUtils.formatNumberToE164(subscriptionInfo.getNumber(), + subscriptionInfo.getCountryIso().toUpperCase(Locale.US)); + } + + private void writeSmsToWriter(JsonWriter jsonWriter, Cursor cursor) throws IOException { + jsonWriter.beginObject(); + + for (int i=0; i<cursor.getColumnCount(); ++i) { + final String name = cursor.getColumnName(i); + final String value = cursor.getString(i); + if (value == null) { + continue; + } + switch (name) { + case Telephony.Sms.SUBSCRIPTION_ID: + final int subId = cursor.getInt(i); + final String selfNumber = mSubId2phone.get(subId); + if (selfNumber != null) { + jsonWriter.name(SELF_PHONE_KEY).value(selfNumber); + } + break; + case Telephony.Sms.THREAD_ID: + final long threadId = cursor.getLong(i); + handleThreadId(jsonWriter, threadId); + break; + case Telephony.Sms._ID: + break; + default: + jsonWriter.name(name).value(value); + break; + } + } + jsonWriter.endObject(); + + } + + private void handleThreadId(JsonWriter jsonWriter, long threadId) throws IOException { + final List<String> recipients = getRecipientsByThread(threadId); + if (recipients == null || recipients.isEmpty()) { + return; + } + + writeRecipientsToWriter(jsonWriter.name(RECIPIENTS), recipients); + if (!mThreadArchived.containsKey(threadId)) { + boolean isArchived = isThreadArchived(threadId); + if (isArchived) { + jsonWriter.name(Telephony.Threads.ARCHIVED).value(true); + } + mThreadArchived.put(threadId, isArchived); + } + } + + private static String[] THREAD_ARCHIVED_PROJECTION = + new String[] { Telephony.Threads.ARCHIVED }; + private static int THREAD_ARCHIVED_IDX = 0; + + private boolean isThreadArchived(long threadId) { + Uri.Builder builder = Telephony.Threads.CONTENT_URI.buildUpon(); + builder.appendPath(String.valueOf(threadId)).appendPath("recipients"); + Uri uri = builder.build(); + + try (Cursor cursor = getContentResolver().query(uri, THREAD_ARCHIVED_PROJECTION, null, null, + null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(THREAD_ARCHIVED_IDX) == 1; + } + } + return false; + } + + private static void writeRecipientsToWriter(JsonWriter jsonWriter, List<String> recipients) + throws IOException { + jsonWriter.beginArray(); + if (recipients != null) { + for (String s : recipients) { + jsonWriter.value(s); + } + } + jsonWriter.endArray(); + } + + private ContentValues readSmsValuesFromReader(JsonReader jsonReader) + throws IOException { + ContentValues values = new ContentValues(6+sDefaultValuesSms.size()); + values.putAll(sDefaultValuesSms); + long threadId = -1; + boolean isArchived = false; + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + switch (name) { + case Telephony.Sms.BODY: + case Telephony.Sms.DATE: + case Telephony.Sms.DATE_SENT: + case Telephony.Sms.STATUS: + case Telephony.Sms.TYPE: + case Telephony.Sms.SUBJECT: + case Telephony.Sms.ADDRESS: + values.put(name, jsonReader.nextString()); + break; + case RECIPIENTS: + threadId = getOrCreateThreadId(getRecipients(jsonReader)); + values.put(Telephony.Sms.THREAD_ID, threadId); + break; + case Telephony.Threads.ARCHIVED: + isArchived = jsonReader.nextBoolean(); + break; + case SELF_PHONE_KEY: + final String selfPhone = jsonReader.nextString(); + if (mPhone2subId.containsKey(selfPhone)) { + values.put(Telephony.Sms.SUBSCRIPTION_ID, mPhone2subId.get(selfPhone)); + } + break; + default: + if (DEBUG) { + Log.w(TAG, "Unknown name:" + name); + } + jsonReader.skipValue(); + break; + } + } + jsonReader.endObject(); + archiveThread(threadId, isArchived); + return values; + } + + private static Set<String> getRecipients(JsonReader jsonReader) throws IOException { + Set<String> recipients = new ArraySet<String>(); + jsonReader.beginArray(); + while (jsonReader.hasNext()) { + recipients.add(jsonReader.nextString()); + } + jsonReader.endArray(); + return recipients; + } + + private int writeMmsToWriter(JsonWriter jsonWriter, Cursor cursor) throws IOException { + final int mmsId = cursor.getInt(ID_IDX); + final MmsBody body = getMmsBody(mmsId); + if (body == null || body.text == null) { + return 0; + } + + boolean subjectNull = true; + jsonWriter.beginObject(); + for (int i=0; i<cursor.getColumnCount(); ++i) { + final String name = cursor.getColumnName(i); + final String value = cursor.getString(i); + if (value == null) { + continue; + } + switch (name) { + case Telephony.Mms.SUBSCRIPTION_ID: + final int subId = cursor.getInt(i); + final String selfNumber = mSubId2phone.get(subId); + if (selfNumber != null) { + jsonWriter.name(SELF_PHONE_KEY).value(selfNumber); + } + break; + case Telephony.Mms.THREAD_ID: + final long threadId = cursor.getLong(i); + handleThreadId(jsonWriter, threadId); + break; + case Telephony.Mms._ID: + case Telephony.Mms.SUBJECT_CHARSET: + break; + case Telephony.Mms.SUBJECT: + subjectNull = false; + default: + jsonWriter.name(name).value(value); + break; + } + } + // Addresses. + writeMmsAddresses(jsonWriter.name(MMS_ADDRESSES_KEY), mmsId); + // Body (text of the message). + jsonWriter.name(MMS_BODY_KEY).value(body.text); + // Charset of the body text. + jsonWriter.name(MMS_BODY_CHARSET_KEY).value(body.charSet); + + if (!subjectNull) { + // Subject charset. + writeStringToWriter(jsonWriter, cursor, Telephony.Mms.SUBJECT_CHARSET); + } + jsonWriter.endObject(); + return 1; + } + + private Mms readMmsFromReader(JsonReader jsonReader) throws IOException { + Mms mms = new Mms(); + mms.values = new ContentValues(5+sDefaultValuesMms.size()); + mms.values.putAll(sDefaultValuesMms); + jsonReader.beginObject(); + String bodyText = null; + long threadId = -1; + boolean isArchived = false; + int bodyCharset = CharacterSets.DEFAULT_CHARSET; + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + switch (name) { + case SELF_PHONE_KEY: + final String selfPhone = jsonReader.nextString(); + if (mPhone2subId.containsKey(selfPhone)) { + mms.values.put(Telephony.Mms.SUBSCRIPTION_ID, mPhone2subId.get(selfPhone)); + } + break; + case MMS_ADDRESSES_KEY: + getMmsAddressesFromReader(jsonReader, mms); + break; + case MMS_BODY_KEY: + bodyText = jsonReader.nextString(); + break; + case MMS_BODY_CHARSET_KEY: + bodyCharset = jsonReader.nextInt(); + break; + case RECIPIENTS: + threadId = getOrCreateThreadId(getRecipients(jsonReader)); + mms.values.put(Telephony.Sms.THREAD_ID, threadId); + break; + case Telephony.Threads.ARCHIVED: + isArchived = jsonReader.nextBoolean(); + break; + case Telephony.Mms.SUBJECT: + case Telephony.Mms.SUBJECT_CHARSET: + case Telephony.Mms.DATE: + case Telephony.Mms.DATE_SENT: + case Telephony.Mms.MESSAGE_TYPE: + case Telephony.Mms.MMS_VERSION: + case Telephony.Mms.MESSAGE_BOX: + case Telephony.Mms.CONTENT_LOCATION: + case Telephony.Mms.TRANSACTION_ID: + mms.values.put(name, jsonReader.nextString()); + break; + default: + if (DEBUG) { + Log.w(TAG, "Unknown name:" + name); + } + jsonReader.skipValue(); + break; + } + } + jsonReader.endObject(); + + if (bodyText != null) { + mms.body = new MmsBody(bodyText, bodyCharset); + } + + // Set default charset for subject. + if (mms.values.get(Telephony.Mms.SUBJECT) != null && + mms.values.get(Telephony.Mms.SUBJECT_CHARSET) == null) { + mms.values.put(Telephony.Mms.SUBJECT_CHARSET, CharacterSets.DEFAULT_CHARSET); + } + + archiveThread(threadId, isArchived); + + return mms; + } + + private static final String ARCHIVE_THREAD_SELECTION = Telephony.Threads._ID + "=?"; + + private void archiveThread(long threadId, boolean isArchived) { + if (threadId < 0 || !isArchived) { + return; + } + final ContentValues values = new ContentValues(1); + values.put(Telephony.Threads.ARCHIVED, 1); + if (mContentResolver.update( + Telephony.Threads.CONTENT_URI, + values, + ARCHIVE_THREAD_SELECTION, + new String[] { Long.toString(threadId)}) != 1) { + if (DEBUG) { + Log.e(TAG, "archiveThread: failed to update database"); + } + } + } + + private MmsBody getMmsBody(int mmsId) { + Uri MMS_PART_CONTENT_URI = Telephony.Mms.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(mmsId)).appendPath("part").build(); + + String body = null; + int charSet = 0; + + try (Cursor cursor = mContentResolver.query(MMS_PART_CONTENT_URI, MMS_TEXT_PROJECTION, + Telephony.Mms.Part.CONTENT_TYPE + "=?", new String[]{ContentType.TEXT_PLAIN}, + ORDER_BY_ID)) { + if (cursor != null && cursor.moveToFirst()) { + do { + body = (body == null ? cursor.getString(MMS_TEXT_IDX) + : body.concat(cursor.getString(MMS_TEXT_IDX))); + charSet = cursor.getInt(MMS_TEXT_CHARSET_IDX); + } while (cursor.moveToNext()); + } + } + return (body == null ? null : new MmsBody(body, charSet)); + } + + private void writeMmsAddresses(JsonWriter jsonWriter, int mmsId) throws IOException { + Uri.Builder builder = Telephony.Mms.CONTENT_URI.buildUpon(); + builder.appendPath(String.valueOf(mmsId)).appendPath("addr"); + Uri uriAddrPart = builder.build(); + + jsonWriter.beginArray(); + try (Cursor cursor = mContentResolver.query(uriAddrPart, MMS_ADDR_PROJECTION, + null/*selection*/, null/*selectionArgs*/, ORDER_BY_ID)) { + if (cursor != null && cursor.moveToFirst()) { + do { + if (cursor.getString(cursor.getColumnIndex(Telephony.Mms.Addr.ADDRESS)) + != null) { + jsonWriter.beginObject(); + writeIntToWriter(jsonWriter, cursor, Telephony.Mms.Addr.TYPE); + writeStringToWriter(jsonWriter, cursor, Telephony.Mms.Addr.ADDRESS); + writeIntToWriter(jsonWriter, cursor, Telephony.Mms.Addr.CHARSET); + jsonWriter.endObject(); + } + } while (cursor.moveToNext()); + } + } + jsonWriter.endArray(); + } + + private static void getMmsAddressesFromReader(JsonReader jsonReader, Mms mms) + throws IOException { + mms.addresses = new ArrayList<ContentValues>(); + jsonReader.beginArray(); + while (jsonReader.hasNext()) { + jsonReader.beginObject(); + ContentValues addrValues = new ContentValues(sDefaultValuesAddr); + while (jsonReader.hasNext()) { + final String name = jsonReader.nextName(); + switch (name) { + case Telephony.Mms.Addr.TYPE: + case Telephony.Mms.Addr.CHARSET: + addrValues.put(name, jsonReader.nextInt()); + break; + case Telephony.Mms.Addr.ADDRESS: + addrValues.put(name, jsonReader.nextString()); + break; + default: + if (DEBUG) { + Log.w(TAG, "Unknown name:" + name); + } + jsonReader.skipValue(); + break; + } + } + jsonReader.endObject(); + if (addrValues.containsKey(Telephony.Mms.Addr.ADDRESS)) { + mms.addresses.add(addrValues); + } + } + jsonReader.endArray(); + } + + private void addMmsMessage(Mms mms) { + if (DEBUG) { + Log.e(TAG, "Add mms:\n" + mms.toString()); + } + final long dummyId = System.currentTimeMillis(); // Dummy ID of the msg. + final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(dummyId)).appendPath("part").build(); + + final String srcName = String.format(Locale.US, "text.%06d.txt", 0); + { // Insert SMIL part. + final String smilBody = String.format(sSmilTextPart, srcName); + final String smil = String.format(sSmilTextOnly, smilBody); + final ContentValues values = new ContentValues(7); + values.put(Telephony.Mms.Part.MSG_ID, dummyId); + values.put(Telephony.Mms.Part.SEQ, -1); + values.put(Telephony.Mms.Part.CONTENT_TYPE, ContentType.APP_SMIL); + values.put(Telephony.Mms.Part.NAME, "smil.xml"); + values.put(Telephony.Mms.Part.CONTENT_ID, "<smil>"); + values.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml"); + values.put(Telephony.Mms.Part.TEXT, smil); + if (mContentResolver.insert(partUri, values) == null) { + if (DEBUG) { + Log.e(TAG, "Could not insert SMIL part"); + } + return; + } + } + + { // Insert body part. + final ContentValues values = new ContentValues(8); + values.put(Telephony.Mms.Part.MSG_ID, dummyId); + values.put(Telephony.Mms.Part.SEQ, 0); + values.put(Telephony.Mms.Part.CONTENT_TYPE, ContentType.TEXT_PLAIN); + values.put(Telephony.Mms.Part.NAME, srcName); + values.put(Telephony.Mms.Part.CONTENT_ID, "<"+srcName+">"); + values.put(Telephony.Mms.Part.CONTENT_LOCATION, srcName); + values.put(Telephony.Mms.Part.CHARSET, mms.body.charSet); + values.put(Telephony.Mms.Part.TEXT, mms.body.text); + if (mContentResolver.insert(partUri, values) == null) { + if (DEBUG) { + Log.e(TAG, "Could not insert body part"); + } + return; + } + } + + // Insert mms. + final Uri mmsUri = mContentResolver.insert(Telephony.Mms.CONTENT_URI, mms.values); + if (mmsUri == null) { + if (DEBUG) { + Log.e(TAG, "Could not insert mms"); + } + return; + } + + final long mmsId = ContentUris.parseId(mmsUri); + { // Update parts with the right mms id. + ContentValues values = new ContentValues(1); + values.put(Telephony.Mms.Part.MSG_ID, mmsId); + mContentResolver.update(partUri, values, null, null); + } + + { // Insert adderesses into "addr". + final Uri addrUri = Uri.withAppendedPath(mmsUri, "addr"); + for (ContentValues mmsAddress : mms.addresses) { + ContentValues values = new ContentValues(mmsAddress); + values.put(Telephony.Mms.Addr.MSG_ID, mmsId); + mContentResolver.insert(addrUri, values); + } + } + } + + private static final class MmsBody { + public String text; + public int charSet; + + public MmsBody(String text, int charSet) { + this.text = text; + this.charSet = charSet; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MmsBody)) { + return false; + } + MmsBody typedObj = (MmsBody) obj; + return this.text.equals(typedObj.text) && this.charSet == typedObj.charSet; + } + + @Override + public String toString() { + return "Text:" + text + " charSet:" + charSet; + } + } + + private static final class Mms { + public ContentValues values; + public List<ContentValues> addresses; + public MmsBody body; + @Override + public String toString() { + return "Values:" + values.toString() + "\nRecipients:"+addresses.toString() + + "\nBody:" + body; + } + } + + private JsonWriter getJsonWriter(final String fileName) throws IOException { + return new JsonWriter(new BufferedWriter(new OutputStreamWriter(new DeflaterOutputStream( + openFileOutput(fileName, MODE_PRIVATE)), CHARSET_UTF8), WRITER_BUFFER_SIZE)); + } + + private static JsonReader getJsonReader(final FileDescriptor fileDescriptor) + throws IOException { + return new JsonReader(new InputStreamReader(new InflaterInputStream( + new FileInputStream(fileDescriptor)), CHARSET_UTF8)); + } + + private static void writeStringToWriter(JsonWriter jsonWriter, Cursor cursor, String name) + throws IOException { + final String value = cursor.getString(cursor.getColumnIndex(name)); + if (value != null) { + jsonWriter.name(name).value(value); + } + } + + private static void writeIntToWriter(JsonWriter jsonWriter, Cursor cursor, String name) + throws IOException { + final int value = cursor.getInt(cursor.getColumnIndex(name)); + if (value != 0) { + jsonWriter.name(name).value(value); + } + } + + private long getOrCreateThreadId(Set<String> recipients) { + if (recipients == null) { + recipients = new ArraySet<String>(); + } + + if (recipients.isEmpty()) { + recipients.add(UNKNOWN_SENDER); + } + + if (mCacheGetOrCreateThreadId == null) { + mCacheGetOrCreateThreadId = new HashMap<>(); + } + + if (!mCacheGetOrCreateThreadId.containsKey(recipients)) { + long threadId = mUnknownSenderThreadId; + try { + threadId = Telephony.Threads.getOrCreateThreadId(this, recipients); + } catch (RuntimeException e) { + if (DEBUG) { + Log.e(TAG, e.toString()); + } + } + mCacheGetOrCreateThreadId.put(recipients, threadId); + return threadId; + } + + return mCacheGetOrCreateThreadId.get(recipients); + } + + @VisibleForTesting + static final Uri THREAD_ID_CONTENT_URI = Uri.parse("content://mms-sms/threadID"); + + // Mostly copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + private List<String> getRecipientsByThread(final long threadId) { + if (mCacheRecipientsByThread == null) { + mCacheRecipientsByThread = new HashMap<>(); + } + + if (!mCacheRecipientsByThread.containsKey(threadId)) { + final String spaceSepIds = getRawRecipientIdsForThread(threadId); + if (!TextUtils.isEmpty(spaceSepIds)) { + mCacheRecipientsByThread.put(threadId, getAddresses(spaceSepIds)); + } else { + mCacheRecipientsByThread.put(threadId, new ArrayList<String>()); + } + } + + return mCacheRecipientsByThread.get(threadId); + } + + @VisibleForTesting + static final Uri ALL_THREADS_URI = + Telephony.Threads.CONTENT_URI.buildUpon(). + appendQueryParameter("simple", "true").build(); + private static final int RECIPIENT_IDS = 1; + + // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + // NOTE: There are phones on which you can't get the recipients from the thread id for SMS + // until you have a message in the conversation! + private String getRawRecipientIdsForThread(final long threadId) { + if (threadId <= 0) { + return null; + } + final Cursor thread = mContentResolver.query( + ALL_THREADS_URI, + SMS_RECIPIENTS_PROJECTION, "_id=?", new String[]{String.valueOf(threadId)}, null); + if (thread != null) { + try { + if (thread.moveToFirst()) { + // recipientIds will be a space-separated list of ids into the + // canonical addresses table. + return thread.getString(RECIPIENT_IDS); + } + } finally { + thread.close(); + } + } + return null; + } + + @VisibleForTesting + static final Uri SINGLE_CANONICAL_ADDRESS_URI = + Uri.parse("content://mms-sms/canonical-address"); + + // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java. + private List<String> getAddresses(final String spaceSepIds) { + final List<String> numbers = new ArrayList<String>(); + final String[] ids = spaceSepIds.split(" "); + for (final String id : ids) { + long longId; + + try { + longId = Long.parseLong(id); + if (longId < 0) { + if (DEBUG) { + Log.e(TAG, "getAddresses: invalid id " + longId); + } + continue; + } + } catch (final NumberFormatException ex) { + if (DEBUG) { + Log.e(TAG, "getAddresses: invalid id. " + ex, ex); + } + // skip this id + continue; + } + + // TODO: build a single query where we get all the addresses at once. + Cursor c = null; + try { + c = mContentResolver.query( + ContentUris.withAppendedId(SINGLE_CANONICAL_ADDRESS_URI, longId), + null, null, null, null); + } catch (final Exception e) { + if (DEBUG) { + Log.e(TAG, "getAddresses: query failed for id " + longId, e); + } + } + if (c != null) { + try { + if (c.moveToFirst()) { + final String number = c.getString(0); + if (!TextUtils.isEmpty(number)) { + numbers.add(number); + } else { + if (DEBUG) { + Log.w(TAG, "Canonical MMS/SMS address is empty for id: " + longId); + } + } + } + } finally { + c.close(); + } + } + } + if (numbers.isEmpty()) { + if (DEBUG) { + Log.w(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]"); + } + } + return numbers; + } + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) throws IOException { + // Empty because is not used during full backup. + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, + ParcelFileDescriptor newState) throws IOException { + // Empty because is not used during full restore. + } +} diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java index bf77aec..97e9b69 100644 --- a/src/com/android/providers/telephony/TelephonyProvider.java +++ b/src/com/android/providers/telephony/TelephonyProvider.java @@ -35,9 +35,9 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.FileUtils; import android.os.SystemProperties; import android.os.UserHandle; -import android.provider.Telephony; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -55,19 +55,20 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.lang.NumberFormatException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import static android.provider.Telephony.Carriers.*; + public class TelephonyProvider extends ContentProvider { private static final String DATABASE_NAME = "telephony.db"; private static final boolean DBG = true; private static final boolean VDBG = false; // STOPSHIP if true - private static final int DATABASE_VERSION = 17 << 16; + private static final int DATABASE_VERSION = 18 << 16; private static final int URL_UNKNOWN = 0; private static final int URL_TELEPHONY = 1; private static final int URL_CURRENT = 2; @@ -89,7 +90,7 @@ public class TelephonyProvider extends ContentProvider private static final String CARRIERS_TABLE_TMP = "carriers_tmp"; private static final String SIMINFO_TABLE = "siminfo"; - private static final String PREF_FILE = "preferred-apn"; + private static final String PREF_FILE_APN = "preferred-apn"; private static final String COLUMN_APN_ID = "apn_id"; private static final String PREF_FILE_FULL_APN = "preferred-full-apn"; @@ -98,6 +99,9 @@ public class TelephonyProvider extends ContentProvider private static final String BUILD_ID_FILE = "build-id"; private static final String RO_BUILD_ID = "ro_build_id"; + private static final String PREF_FILE = "telephonyprovider"; + private static final String APN_CONF_CHECKSUM = "apn_conf_checksum"; + private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml"; private static final String OEM_APNS_PATH = "telephony/apns-conf.xml"; private static final String OTA_UPDATED_APNS_PATH = "misc/apns-conf.xml"; @@ -108,27 +112,44 @@ public class TelephonyProvider extends ContentProvider private static final ContentValues s_currentNullMap; private static final ContentValues s_currentSetMap; + private static final String IS_UNEDITED = EDITED + "=" + UNEDITED; + private static final String IS_EDITED = EDITED + "!=" + UNEDITED; + private static final String IS_USER_EDITED = EDITED + "=" + USER_EDITED; + private static final String IS_USER_DELETED = EDITED + "=" + USER_DELETED; + private static final String IS_NOT_USER_DELETED = EDITED + "!=" + USER_DELETED; + private static final String IS_USER_DELETED_BUT_PRESENT_IN_XML = + EDITED + "=" + USER_DELETED_BUT_PRESENT_IN_XML; + private static final String IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML = + EDITED + "!=" + USER_DELETED_BUT_PRESENT_IN_XML; + private static final String IS_CARRIER_EDITED = EDITED + "=" + CARRIER_EDITED; + private static final String IS_CARRIER_DELETED = EDITED + "=" + CARRIER_DELETED; + private static final String IS_NOT_CARRIER_DELETED = EDITED + "!=" + CARRIER_DELETED; + private static final String IS_CARRIER_DELETED_BUT_PRESENT_IN_XML = + EDITED + "=" + CARRIER_DELETED_BUT_PRESENT_IN_XML; + private static final String IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML = + EDITED + "!=" + CARRIER_DELETED_BUT_PRESENT_IN_XML; + private static final int INVALID_APN_ID = -1; private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>(); static { // Columns not included in UNIQUE constraint: name, current, edited, user, server, password, - // authtype, type, protocol, roaming_protocol, sub_id, modem_cognitive, max_conns, wait_time, - // max_conns_time, mtu, bearer_bitmask, user_visible - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.NUMERIC); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MCC); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MNC); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.APN); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.PROXY); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.PORT); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MMSPROXY); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MMSPORT); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MMSC); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.CARRIER_ENABLED); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.BEARER); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MVNO_TYPE); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.MVNO_MATCH_DATA); - CARRIERS_UNIQUE_FIELDS.add(Telephony.Carriers.PROFILE_ID); + // authtype, type, protocol, roaming_protocol, sub_id, modem_cognitive, max_conns, + // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible + CARRIERS_UNIQUE_FIELDS.add(NUMERIC); + CARRIERS_UNIQUE_FIELDS.add(MCC); + CARRIERS_UNIQUE_FIELDS.add(MNC); + CARRIERS_UNIQUE_FIELDS.add(APN); + CARRIERS_UNIQUE_FIELDS.add(PROXY); + CARRIERS_UNIQUE_FIELDS.add(PORT); + CARRIERS_UNIQUE_FIELDS.add(MMSPROXY); + CARRIERS_UNIQUE_FIELDS.add(MMSPORT); + CARRIERS_UNIQUE_FIELDS.add(MMSC); + CARRIERS_UNIQUE_FIELDS.add(CARRIER_ENABLED); + CARRIERS_UNIQUE_FIELDS.add(BEARER); + CARRIERS_UNIQUE_FIELDS.add(MVNO_TYPE); + CARRIERS_UNIQUE_FIELDS.add(MVNO_MATCH_DATA); + CARRIERS_UNIQUE_FIELDS.add(PROFILE_ID); } static { @@ -151,10 +172,10 @@ public class TelephonyProvider extends ContentProvider s_urlMatcher.addURI("telephony", "carriers/update_db", URL_UPDATE_DB); s_currentNullMap = new ContentValues(1); - s_currentNullMap.put(Telephony.Carriers.CURRENT, "0"); + s_currentNullMap.put(CURRENT, "0"); s_currentSetMap = new ContentValues(1); - s_currentSetMap.put(Telephony.Carriers.CURRENT, "1"); + s_currentSetMap.put(CURRENT, "1"); } private static class DatabaseHelper extends SQLiteOpenHelper { @@ -240,6 +261,7 @@ public class TelephonyProvider extends ContentProvider + SubscriptionManager.DATA_ROAMING + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + "," + SubscriptionManager.MCC + " INTEGER DEFAULT 0," + SubscriptionManager.MNC + " INTEGER DEFAULT 0," + + SubscriptionManager.SIM_PROVISIONING_STATUS + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + "," + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1," + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1," + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1," @@ -261,49 +283,103 @@ public class TelephonyProvider extends ContentProvider if (DBG) log("dbh.createCarriersTable: " + tableName); db.execSQL("CREATE TABLE " + tableName + "(_id INTEGER PRIMARY KEY," + - "name TEXT DEFAULT ''," + - "numeric TEXT DEFAULT ''," + - "mcc TEXT DEFAULT ''," + - "mnc TEXT DEFAULT ''," + - "apn TEXT DEFAULT ''," + - "user TEXT DEFAULT ''," + - "server TEXT DEFAULT ''," + - "password TEXT DEFAULT ''," + - "proxy TEXT DEFAULT ''," + - "port TEXT DEFAULT ''," + - "mmsproxy TEXT DEFAULT ''," + - "mmsport TEXT DEFAULT ''," + - "mmsc TEXT DEFAULT ''," + - "authtype INTEGER DEFAULT -1," + - "type TEXT DEFAULT ''," + - "current INTEGER," + - "protocol TEXT DEFAULT 'IP'," + - "roaming_protocol TEXT DEFAULT 'IP'," + - "carrier_enabled BOOLEAN DEFAULT 1," + - "bearer INTEGER DEFAULT 0," + - "bearer_bitmask INTEGER DEFAULT 0," + - "mvno_type TEXT DEFAULT ''," + - "mvno_match_data TEXT DEFAULT ''," + - "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," + - "profile_id INTEGER DEFAULT 0," + - "modem_cognitive BOOLEAN DEFAULT 0," + - "max_conns INTEGER DEFAULT 0," + - "wait_time INTEGER DEFAULT 0," + - "max_conns_time INTEGER DEFAULT 0," + - "mtu INTEGER DEFAULT 0," + - "edited INTEGER DEFAULT " + Telephony.Carriers.UNEDITED + "," + - "user_visible BOOLEAN DEFAULT 1," + + NAME + " TEXT DEFAULT ''," + + NUMERIC + " TEXT DEFAULT ''," + + MCC + " TEXT DEFAULT ''," + + MNC + " TEXT DEFAULT ''," + + APN + " TEXT DEFAULT ''," + + USER + " TEXT DEFAULT ''," + + SERVER + " TEXT DEFAULT ''," + + PASSWORD + " TEXT DEFAULT ''," + + PROXY + " TEXT DEFAULT ''," + + PORT + " TEXT DEFAULT ''," + + MMSPROXY + " TEXT DEFAULT ''," + + MMSPORT + " TEXT DEFAULT ''," + + MMSC + " TEXT DEFAULT ''," + + AUTH_TYPE + " INTEGER DEFAULT -1," + + TYPE + " TEXT DEFAULT ''," + + CURRENT + " INTEGER," + + PROTOCOL + " TEXT DEFAULT 'IP'," + + ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," + + CARRIER_ENABLED + " BOOLEAN DEFAULT 1," + + BEARER + " INTEGER DEFAULT 0," + + BEARER_BITMASK + " INTEGER DEFAULT 0," + + MVNO_TYPE + " TEXT DEFAULT ''," + + MVNO_MATCH_DATA + " TEXT DEFAULT ''," + + SUBSCRIPTION_ID + " INTEGER DEFAULT " + + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," + + PROFILE_ID + " INTEGER DEFAULT 0," + + MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," + + MAX_CONNS + " INTEGER DEFAULT 0," + + WAIT_TIME + " INTEGER DEFAULT 0," + + MAX_CONNS_TIME + " INTEGER DEFAULT 0," + + MTU + " INTEGER DEFAULT 0," + + EDITED + " INTEGER DEFAULT " + UNEDITED + "," + + USER_VISIBLE + " BOOLEAN DEFAULT 1," + // Uniqueness collisions are used to trigger merge code so if a field is listed // here it means we will accept both (user edited + new apn_conf definition) // Columns not included in UNIQUE constraint: name, current, edited, // user, server, password, authtype, type, protocol, roaming_protocol, sub_id, // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask, // user_visible - "UNIQUE (numeric, mcc, mnc, apn, proxy, port, mmsproxy, mmsport, mmsc," + - "carrier_enabled, bearer, mvno_type, mvno_match_data, profile_id));"); + "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));"); if (DBG) log("dbh.createCarriersTable:-"); } + private long getChecksum(File file) { + long checksum = -1; + try { + checksum = FileUtils.checksumCrc32(file); + if (DBG) log("Checksum for " + file.getAbsolutePath() + " is " + checksum); + } catch (FileNotFoundException e) { + loge("FileNotFoundException for " + file.getAbsolutePath() + ":" + e); + } catch (IOException e) { + loge("IOException for " + file.getAbsolutePath() + ":" + e); + } + return checksum; + } + + private long getApnConfChecksum() { + SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + return sp.getLong(APN_CONF_CHECKSUM, -1); + } + + private void setApnConfChecksum(long checksum) { + SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.putLong(APN_CONF_CHECKSUM, checksum); + editor.apply(); + } + + private File getApnConfFile() { + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH); + File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH); + File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH); + confFile = getNewerFile(confFile, oemConfFile); + confFile = getNewerFile(confFile, updatedConfFile); + return confFile; + } + + /** + * This function computes checksum for the file to be read and compares it against the + * last read file. DB needs to be updated only if checksum has changed, or old checksum does + * not exist. + * @return true if DB should be updated with new conf file, false otherwise + */ + private boolean apnDbUpdateNeeded() { + File confFile = getApnConfFile(); + long newChecksum = getChecksum(confFile); + long oldChecksum = getApnConfChecksum(); + if (DBG) log("newChecksum: " + newChecksum); + if (DBG) log("oldChecksum: " + oldChecksum); + if (newChecksum == oldChecksum) { + return false; + } else { + return true; + } + } + /** * This function adds APNs from xml file(s) to db. The db may or may not be empty to begin * with. @@ -326,12 +402,7 @@ public class TelephonyProvider extends ContentProvider // Read external APNS data (partner-provided) XmlPullParser confparser = null; - // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". - File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH); - File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH); - File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH); - confFile = getNewerFile(confFile, oemConfFile); - confFile = getNewerFile(confFile, updatedConfFile); + File confFile = getApnConfFile(); FileReader confreader = null; if (DBG) log("confFile = " + confFile); @@ -365,20 +436,17 @@ public class TelephonyProvider extends ContentProvider } // Delete USER_DELETED - db.delete(CARRIERS_TABLE, "edited=" + Telephony.Carriers.USER_DELETED + " or " + - "edited=" + Telephony.Carriers.CARRIER_DELETED, null); + db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null); // Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED ContentValues cv = new ContentValues(); - cv.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_DELETED); - db.update(CARRIERS_TABLE, cv, "edited=" + Telephony.Carriers.USER_DELETED_BUT_PRESENT_IN_XML, - null); + cv.put(EDITED, USER_DELETED); + db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null); // Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED cv = new ContentValues(); - cv.put(Telephony.Carriers.EDITED, Telephony.Carriers.CARRIER_DELETED); - db.update(CARRIERS_TABLE, cv, "edited=" + Telephony.Carriers.CARRIER_DELETED_BUT_PRESENT_IN_XML, - null); + cv.put(EDITED, CARRIER_DELETED); + db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null); if (confreader != null) { try { @@ -387,6 +455,9 @@ public class TelephonyProvider extends ContentProvider // do nothing } } + + // Update the stored checksum + setApnConfChecksum(getChecksum(confFile)); } if (VDBG) log("dbh.initDatabase:- db=" + db); @@ -571,15 +642,13 @@ public class TelephonyProvider extends ContentProvider c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null); log("dbh.onUpgrade:- after upgrading total number of rows: " + c.getCount()); c.close(); - c = db.query(CARRIERS_TABLE, proj, "edited=" + Telephony.Carriers.UNEDITED, - null, null, null, null); - log("dbh.onUpgrade:- after upgrading total number of rows with edited=" - + Telephony.Carriers.UNEDITED + ": " + c.getCount()); + c = db.query(CARRIERS_TABLE, proj, IS_UNEDITED, null, null, null, null); + log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_UNEDITED + + ": " + c.getCount()); c.close(); - c = db.query(CARRIERS_TABLE, proj, "edited!=" + Telephony.Carriers.UNEDITED, - null, null, null, null); - log("dbh.onUpgrade:- after upgrading total number of rows with edited!=" - + Telephony.Carriers.UNEDITED + ": " + c.getCount()); + c = db.query(CARRIERS_TABLE, proj, IS_EDITED, null, null, null, null); + log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_EDITED + + ": " + c.getCount()); c.close(); } @@ -627,13 +696,13 @@ public class TelephonyProvider extends ContentProvider try { c = db.query(CARRIERS_TABLE, null, null, null, null, null, null, String.valueOf(1)); - if (c == null || c.getColumnIndex(Telephony.Carriers.USER_VISIBLE) == -1) { + if (c == null || c.getColumnIndex(USER_VISIBLE) == -1) { db.execSQL("ALTER TABLE " + CARRIERS_TABLE + " ADD COLUMN " + - Telephony.Carriers.USER_VISIBLE + " BOOLEAN DEFAULT 1;"); + USER_VISIBLE + " BOOLEAN DEFAULT 1;"); } else { if (DBG) { log("onUpgrade skipping " + CARRIERS_TABLE + " upgrade. Column " + - Telephony.Carriers.USER_VISIBLE + " already exists."); + USER_VISIBLE + " already exists."); } } } finally { @@ -643,6 +712,20 @@ public class TelephonyProvider extends ContentProvider } oldVersion = 17 << 16 | 6; } + if (oldVersion < (18 << 16 | 6)) { + try { + // Try to update the siminfo table. It might not be there. + db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN " + + SubscriptionManager.SIM_PROVISIONING_STATUS + " INTEGER DEFAULT " + + SubscriptionManager.SIM_PROVISIONED + ";"); + } catch (SQLiteException e) { + if (DBG) { + log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " + + " The table will get created in onOpen."); + } + } + oldVersion = 18 << 16 | 6; + } if (DBG) { log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion); } @@ -663,12 +746,11 @@ public class TelephonyProvider extends ContentProvider } catch (FileNotFoundException e) { // This function is called only when upgrading db to version 15. Details about the // upgrade are mentioned in onUpgrade(). This file missing means user/carrier added - // APNs cannot be preserved. Throw an exception so that OEMs know they need to + // APNs cannot be preserved. Log an error message so that OEMs know they need to // include old apns file for comparison. - loge("preserveUserAndCarrierApns: FileNotFoundException"); - throw new RuntimeException("preserveUserAndCarrierApns: " + OLD_APNS_PATH + - " not found. It is needed to upgrade from older versions of APN " + - "db while preserving user/carrier added/edited entries."); + loge("PRESERVEUSERANDCARRIERAPNS: " + OLD_APNS_PATH + + " NOT FOUND. IT IS NEEDED TO UPGRADE FROM OLDER VERSIONS OF APN " + + "DB WHILE PRESERVING USER/CARRIER ADDED/EDITED ENTRIES."); } catch (Exception e) { loge("preserveUserAndCarrierApns: Exception while parsing '" + confFile.getAbsolutePath() + "'" + e); @@ -707,62 +789,87 @@ public class TelephonyProvider extends ContentProvider } } + private String queryValFirst(String field) { + return field + "=?"; + } + + private String queryVal(String field) { + return " and " + field + "=?"; + } + + private String queryValOrNull(String field) { + return " and (" + field + "=? or " + field + " is null)"; + } + + private String queryVal2OrNull(String field) { + return " and (" + field + "=? or " + field + "=? or " + field + " is null)"; + } + private void deleteRow(SQLiteDatabase db, ContentValues values) { if (VDBG) log("deleteRow"); - String where = "numeric=? and mcc=? and mnc=? and name=? and " + - "(apn=? or apn is null) and " + - "(user=? or user is null) and (server=? or server is null) and " + - "(password=? or password is null) and (proxy=? or proxy is null) and " + - "(port=? or port is null) and (mmsproxy=? or mmsproxy is null) and " + - "(mmsport=? or mmsport is null) and (mmsc=? or mmsc is null) and " + - "(authtype=? or authtype is null) and (type=? or type is null) and " + - "(protocol=? or protocol is null) and " + - "(roaming_protocol=? or roaming_protocol is null) and " + - "(carrier_enabled=? or carrier_enabled=? or carrier_enabled is null) and " + - "(bearer=? or bearer is null) and (mvno_type=? or mvno_type is null) and " + - "(mvno_match_data=? or mvno_match_data is null) and " + - "(profile_id=? or profile_id is null) and " + - "(modem_cognitive=? or modem_cognitive=? or modem_cognitive is null) and " + - "(max_conns=? or max_conns is null) and " + - "(wait_time=? or wait_time is null) and " + - "(max_conns_time=? or max_conns_time is null) and (mtu=? or mtu is null)"; + String where = queryValFirst(NUMERIC) + + queryVal(MNC) + + queryVal(MNC) + + queryValOrNull(APN) + + queryValOrNull(USER) + + queryValOrNull(SERVER) + + queryValOrNull(PASSWORD) + + queryValOrNull(PROXY) + + queryValOrNull(PORT) + + queryValOrNull(MMSPROXY) + + queryValOrNull(MMSPORT) + + queryValOrNull(MMSC) + + queryValOrNull(AUTH_TYPE) + + queryValOrNull(TYPE) + + queryValOrNull(PROTOCOL) + + queryValOrNull(ROAMING_PROTOCOL) + + queryVal2OrNull(CARRIER_ENABLED) + + queryValOrNull(BEARER) + + queryValOrNull(MVNO_TYPE) + + queryValOrNull(MVNO_MATCH_DATA) + + queryValOrNull(PROFILE_ID) + + queryVal2OrNull(MODEM_COGNITIVE) + + queryValOrNull(MAX_CONNS) + + queryValOrNull(WAIT_TIME) + + queryValOrNull(MAX_CONNS_TIME) + + queryValOrNull(MTU); String[] whereArgs = new String[29]; int i = 0; - whereArgs[i++] = values.getAsString(Telephony.Carriers.NUMERIC); - whereArgs[i++] = values.getAsString(Telephony.Carriers.MCC); - whereArgs[i++] = values.getAsString(Telephony.Carriers.MNC); - whereArgs[i++] = values.getAsString(Telephony.Carriers.NAME); - whereArgs[i++] = values.containsKey(Telephony.Carriers.APN) ? - values.getAsString(Telephony.Carriers.APN) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.USER) ? - values.getAsString(Telephony.Carriers.USER) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.SERVER) ? - values.getAsString(Telephony.Carriers.SERVER) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.PASSWORD) ? - values.getAsString(Telephony.Carriers.PASSWORD) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.PROXY) ? - values.getAsString(Telephony.Carriers.PROXY) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.PORT) ? - values.getAsString(Telephony.Carriers.PORT) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MMSPROXY) ? - values.getAsString(Telephony.Carriers.MMSPROXY) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MMSPORT) ? - values.getAsString(Telephony.Carriers.MMSPORT) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MMSC) ? - values.getAsString(Telephony.Carriers.MMSC) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.AUTH_TYPE) ? - values.getAsString(Telephony.Carriers.AUTH_TYPE) : "-1"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.TYPE) ? - values.getAsString(Telephony.Carriers.TYPE) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.PROTOCOL) ? - values.getAsString(Telephony.Carriers.PROTOCOL) : "IP"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.ROAMING_PROTOCOL) ? - values.getAsString(Telephony.Carriers.ROAMING_PROTOCOL) : "IP"; - - if (values.containsKey(Telephony.Carriers.CARRIER_ENABLED) && - (values.getAsString(Telephony.Carriers.CARRIER_ENABLED). + whereArgs[i++] = values.getAsString(NUMERIC); + whereArgs[i++] = values.getAsString(MCC); + whereArgs[i++] = values.getAsString(MNC); + whereArgs[i++] = values.getAsString(NAME); + whereArgs[i++] = values.containsKey(APN) ? + values.getAsString(APN) : ""; + whereArgs[i++] = values.containsKey(USER) ? + values.getAsString(USER) : ""; + whereArgs[i++] = values.containsKey(SERVER) ? + values.getAsString(SERVER) : ""; + whereArgs[i++] = values.containsKey(PASSWORD) ? + values.getAsString(PASSWORD) : ""; + whereArgs[i++] = values.containsKey(PROXY) ? + values.getAsString(PROXY) : ""; + whereArgs[i++] = values.containsKey(PORT) ? + values.getAsString(PORT) : ""; + whereArgs[i++] = values.containsKey(MMSPROXY) ? + values.getAsString(MMSPROXY) : ""; + whereArgs[i++] = values.containsKey(MMSPORT) ? + values.getAsString(MMSPORT) : ""; + whereArgs[i++] = values.containsKey(MMSC) ? + values.getAsString(MMSC) : ""; + whereArgs[i++] = values.containsKey(AUTH_TYPE) ? + values.getAsString(AUTH_TYPE) : "-1"; + whereArgs[i++] = values.containsKey(TYPE) ? + values.getAsString(TYPE) : ""; + whereArgs[i++] = values.containsKey(PROTOCOL) ? + values.getAsString(PROTOCOL) : "IP"; + whereArgs[i++] = values.containsKey(ROAMING_PROTOCOL) ? + values.getAsString(ROAMING_PROTOCOL) : "IP"; + + if (values.containsKey(CARRIER_ENABLED) && + (values.getAsString(CARRIER_ENABLED). equalsIgnoreCase("false") || - values.getAsString(Telephony.Carriers.CARRIER_ENABLED).equals("0"))) { + values.getAsString(CARRIER_ENABLED).equals("0"))) { whereArgs[i++] = "false"; whereArgs[i++] = "0"; } else { @@ -770,19 +877,19 @@ public class TelephonyProvider extends ContentProvider whereArgs[i++] = "1"; } - whereArgs[i++] = values.containsKey(Telephony.Carriers.BEARER) ? - values.getAsString(Telephony.Carriers.BEARER) : "0"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MVNO_TYPE) ? - values.getAsString(Telephony.Carriers.MVNO_TYPE) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MVNO_MATCH_DATA) ? - values.getAsString(Telephony.Carriers.MVNO_MATCH_DATA) : ""; - whereArgs[i++] = values.containsKey(Telephony.Carriers.PROFILE_ID) ? - values.getAsString(Telephony.Carriers.PROFILE_ID) : "0"; + whereArgs[i++] = values.containsKey(BEARER) ? + values.getAsString(BEARER) : "0"; + whereArgs[i++] = values.containsKey(MVNO_TYPE) ? + values.getAsString(MVNO_TYPE) : ""; + whereArgs[i++] = values.containsKey(MVNO_MATCH_DATA) ? + values.getAsString(MVNO_MATCH_DATA) : ""; + whereArgs[i++] = values.containsKey(PROFILE_ID) ? + values.getAsString(PROFILE_ID) : "0"; - if (values.containsKey(Telephony.Carriers.MODEM_COGNITIVE) && - (values.getAsString(Telephony.Carriers.MODEM_COGNITIVE). + if (values.containsKey(MODEM_COGNITIVE) && + (values.getAsString(MODEM_COGNITIVE). equalsIgnoreCase("true") || - values.getAsString(Telephony.Carriers.MODEM_COGNITIVE).equals("1"))) { + values.getAsString(MODEM_COGNITIVE).equals("1"))) { whereArgs[i++] = "true"; whereArgs[i++] = "1"; } else { @@ -790,14 +897,14 @@ public class TelephonyProvider extends ContentProvider whereArgs[i++] = "0"; } - whereArgs[i++] = values.containsKey(Telephony.Carriers.MAX_CONNS) ? - values.getAsString(Telephony.Carriers.MAX_CONNS) : "0"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.WAIT_TIME) ? - values.getAsString(Telephony.Carriers.WAIT_TIME) : "0"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MAX_CONNS_TIME) ? - values.getAsString(Telephony.Carriers.MAX_CONNS_TIME) : "0"; - whereArgs[i++] = values.containsKey(Telephony.Carriers.MTU) ? - values.getAsString(Telephony.Carriers.MTU) : "0"; + whereArgs[i++] = values.containsKey(MAX_CONNS) ? + values.getAsString(MAX_CONNS) : "0"; + whereArgs[i++] = values.containsKey(WAIT_TIME) ? + values.getAsString(WAIT_TIME) : "0"; + whereArgs[i++] = values.containsKey(MAX_CONNS_TIME) ? + values.getAsString(MAX_CONNS_TIME) : "0"; + whereArgs[i++] = values.containsKey(MTU) ? + values.getAsString(MTU) : "0"; if (VDBG) { log("deleteRow: where: " + where); @@ -825,74 +932,70 @@ public class TelephonyProvider extends ContentProvider // with default if there's a default value for the field // String vals - getStringValueFromCursor(cv, c, Telephony.Carriers.NAME); - getStringValueFromCursor(cv, c, Telephony.Carriers.NUMERIC); - getStringValueFromCursor(cv, c, Telephony.Carriers.MCC); - getStringValueFromCursor(cv, c, Telephony.Carriers.MNC); - getStringValueFromCursor(cv, c, Telephony.Carriers.APN); - getStringValueFromCursor(cv, c, Telephony.Carriers.USER); - getStringValueFromCursor(cv, c, Telephony.Carriers.SERVER); - getStringValueFromCursor(cv, c, Telephony.Carriers.PASSWORD); - getStringValueFromCursor(cv, c, Telephony.Carriers.PROXY); - getStringValueFromCursor(cv, c, Telephony.Carriers.PORT); - getStringValueFromCursor(cv, c, Telephony.Carriers.MMSPROXY); - getStringValueFromCursor(cv, c, Telephony.Carriers.MMSPORT); - getStringValueFromCursor(cv, c, Telephony.Carriers.MMSC); - getStringValueFromCursor(cv, c, Telephony.Carriers.TYPE); - getStringValueFromCursor(cv, c, Telephony.Carriers.PROTOCOL); - getStringValueFromCursor(cv, c, Telephony.Carriers.ROAMING_PROTOCOL); - getStringValueFromCursor(cv, c, Telephony.Carriers.MVNO_TYPE); - getStringValueFromCursor(cv, c, Telephony.Carriers.MVNO_MATCH_DATA); + getStringValueFromCursor(cv, c, NAME); + getStringValueFromCursor(cv, c, NUMERIC); + getStringValueFromCursor(cv, c, MCC); + getStringValueFromCursor(cv, c, MNC); + getStringValueFromCursor(cv, c, APN); + getStringValueFromCursor(cv, c, USER); + getStringValueFromCursor(cv, c, SERVER); + getStringValueFromCursor(cv, c, PASSWORD); + getStringValueFromCursor(cv, c, PROXY); + getStringValueFromCursor(cv, c, PORT); + getStringValueFromCursor(cv, c, MMSPROXY); + getStringValueFromCursor(cv, c, MMSPORT); + getStringValueFromCursor(cv, c, MMSC); + getStringValueFromCursor(cv, c, TYPE); + getStringValueFromCursor(cv, c, PROTOCOL); + getStringValueFromCursor(cv, c, ROAMING_PROTOCOL); + getStringValueFromCursor(cv, c, MVNO_TYPE); + getStringValueFromCursor(cv, c, MVNO_MATCH_DATA); // bool/int vals - getIntValueFromCursor(cv, c, Telephony.Carriers.AUTH_TYPE); - getIntValueFromCursor(cv, c, Telephony.Carriers.CURRENT); - getIntValueFromCursor(cv, c, Telephony.Carriers.CARRIER_ENABLED); - getIntValueFromCursor(cv, c, Telephony.Carriers.BEARER); - getIntValueFromCursor(cv, c, Telephony.Carriers.SUBSCRIPTION_ID); - getIntValueFromCursor(cv, c, Telephony.Carriers.PROFILE_ID); - getIntValueFromCursor(cv, c, Telephony.Carriers.MODEM_COGNITIVE); - getIntValueFromCursor(cv, c, Telephony.Carriers.MAX_CONNS); - getIntValueFromCursor(cv, c, Telephony.Carriers.WAIT_TIME); - getIntValueFromCursor(cv, c, Telephony.Carriers.MAX_CONNS_TIME); - getIntValueFromCursor(cv, c, Telephony.Carriers.MTU); + getIntValueFromCursor(cv, c, AUTH_TYPE); + getIntValueFromCursor(cv, c, CURRENT); + getIntValueFromCursor(cv, c, CARRIER_ENABLED); + getIntValueFromCursor(cv, c, BEARER); + getIntValueFromCursor(cv, c, SUBSCRIPTION_ID); + getIntValueFromCursor(cv, c, PROFILE_ID); + getIntValueFromCursor(cv, c, MODEM_COGNITIVE); + getIntValueFromCursor(cv, c, MAX_CONNS); + getIntValueFromCursor(cv, c, WAIT_TIME); + getIntValueFromCursor(cv, c, MAX_CONNS_TIME); + getIntValueFromCursor(cv, c, MTU); // Change bearer to a bitmask - String bearerStr = c.getString(c.getColumnIndex(Telephony.Carriers.BEARER)); + String bearerStr = c.getString(c.getColumnIndex(BEARER)); if (!TextUtils.isEmpty(bearerStr)) { int bearer_bitmask = ServiceState.getBitmaskForTech( Integer.parseInt(bearerStr)); - cv.put(Telephony.Carriers.BEARER_BITMASK, bearer_bitmask); + cv.put(BEARER_BITMASK, bearer_bitmask); } int userEditedColumnIdx = c.getColumnIndex("user_edited"); if (userEditedColumnIdx != -1) { String user_edited = c.getString(userEditedColumnIdx); if (!TextUtils.isEmpty(user_edited)) { - cv.put(Telephony.Carriers.EDITED, new Integer(user_edited)); + cv.put(EDITED, new Integer(user_edited)); } } else { - cv.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED); + cv.put(EDITED, USER_EDITED); } - // New EDITED column. Default value (Telephony.Carriers.UNEDITED) will + // New EDITED column. Default value (UNEDITED) will // be used for all rows except for non-mvno entries for plmns indicated // by resource: those will be set to CARRIER_EDITED to preserve // their current values - val = c.getString(c.getColumnIndex(Telephony.Carriers.NUMERIC)); + val = c.getString(c.getColumnIndex(NUMERIC)); for (String s : persistApnsForPlmns) { if (!TextUtils.isEmpty(val) && val.equals(s) && - (!cv.containsKey(Telephony.Carriers.MVNO_TYPE) || - TextUtils.isEmpty(cv.getAsString(Telephony.Carriers. - MVNO_TYPE)))) { + (!cv.containsKey(MVNO_TYPE) || + TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) { if (userEditedColumnIdx == -1) { - cv.put(Telephony.Carriers.EDITED, - Telephony.Carriers.CARRIER_EDITED); + cv.put(EDITED, CARRIER_EDITED); } else { // if (oldVersion == 14) -- if db had user_edited column - if (cv.getAsInteger(Telephony.Carriers.EDITED) == - Telephony.Carriers.USER_EDITED) { - cv.put(Telephony.Carriers.EDITED, - Telephony.Carriers.CARRIER_EDITED); + if (cv.getAsInteger(EDITED) == USER_EDITED) { + cv.put(EDITED, CARRIER_EDITED); } } @@ -961,50 +1064,51 @@ public class TelephonyProvider extends ContentProvider String mnc = parser.getAttributeValue(null, "mnc"); String numeric = mcc + mnc; - map.put(Telephony.Carriers.NUMERIC, numeric); - map.put(Telephony.Carriers.MCC, mcc); - map.put(Telephony.Carriers.MNC, mnc); - map.put(Telephony.Carriers.NAME, parser.getAttributeValue(null, "carrier")); + map.put(NUMERIC, numeric); + map.put(MCC, mcc); + map.put(MNC, mnc); + map.put(NAME, parser.getAttributeValue(null, "carrier")); // do not add NULL to the map so that default values can be inserted in db - addStringAttribute(parser, "apn", map, Telephony.Carriers.APN); - addStringAttribute(parser, "user", map, Telephony.Carriers.USER); - addStringAttribute(parser, "server", map, Telephony.Carriers.SERVER); - addStringAttribute(parser, "password", map, Telephony.Carriers.PASSWORD); - addStringAttribute(parser, "proxy", map, Telephony.Carriers.PROXY); - addStringAttribute(parser, "port", map, Telephony.Carriers.PORT); - addStringAttribute(parser, "mmsproxy", map, Telephony.Carriers.MMSPROXY); - addStringAttribute(parser, "mmsport", map, Telephony.Carriers.MMSPORT); - addStringAttribute(parser, "mmsc", map, Telephony.Carriers.MMSC); - addStringAttribute(parser, "type", map, Telephony.Carriers.TYPE); - addStringAttribute(parser, "protocol", map, Telephony.Carriers.PROTOCOL); - addStringAttribute(parser, "roaming_protocol", map, Telephony.Carriers.ROAMING_PROTOCOL); - - addIntAttribute(parser, "authtype", map, Telephony.Carriers.AUTH_TYPE); - addIntAttribute(parser, "bearer", map, Telephony.Carriers.BEARER); - addIntAttribute(parser, "profile_id", map, Telephony.Carriers.PROFILE_ID); - addIntAttribute(parser, "max_conns", map, Telephony.Carriers.MAX_CONNS); - addIntAttribute(parser, "wait_time", map, Telephony.Carriers.WAIT_TIME); - addIntAttribute(parser, "max_conns_time", map, Telephony.Carriers.MAX_CONNS_TIME); - addIntAttribute(parser, "mtu", map, Telephony.Carriers.MTU); - - - addBoolAttribute(parser, "carrier_enabled", map, Telephony.Carriers.CARRIER_ENABLED); - addBoolAttribute(parser, "modem_cognitive", map, Telephony.Carriers.MODEM_COGNITIVE); - addBoolAttribute(parser, "user_visible", map, Telephony.Carriers.USER_VISIBLE); - + addStringAttribute(parser, "apn", map, APN); + addStringAttribute(parser, "user", map, USER); + addStringAttribute(parser, "server", map, SERVER); + addStringAttribute(parser, "password", map, PASSWORD); + addStringAttribute(parser, "proxy", map, PROXY); + addStringAttribute(parser, "port", map, PORT); + addStringAttribute(parser, "mmsproxy", map, MMSPROXY); + addStringAttribute(parser, "mmsport", map, MMSPORT); + addStringAttribute(parser, "mmsc", map, MMSC); + addStringAttribute(parser, "type", map, TYPE); + addStringAttribute(parser, "protocol", map, PROTOCOL); + addStringAttribute(parser, "roaming_protocol", map, ROAMING_PROTOCOL); + + addIntAttribute(parser, "authtype", map, AUTH_TYPE); + addIntAttribute(parser, "bearer", map, BEARER); + addIntAttribute(parser, "profile_id", map, PROFILE_ID); + addIntAttribute(parser, "max_conns", map, MAX_CONNS); + addIntAttribute(parser, "wait_time", map, WAIT_TIME); + addIntAttribute(parser, "max_conns_time", map, MAX_CONNS_TIME); + addIntAttribute(parser, "mtu", map, MTU); + + + addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED); + addBoolAttribute(parser, "modem_cognitive", map, MODEM_COGNITIVE); + addBoolAttribute(parser, "user_visible", map, USER_VISIBLE); + + int bearerBitmask = 0; String bearerList = parser.getAttributeValue(null, "bearer_bitmask"); if (bearerList != null) { - int bearerBitmask = ServiceState.getBitmaskFromString(bearerList); - map.put(Telephony.Carriers.BEARER_BITMASK, bearerBitmask); + bearerBitmask = ServiceState.getBitmaskFromString(bearerList); } + map.put(BEARER_BITMASK, bearerBitmask); String mvno_type = parser.getAttributeValue(null, "mvno_type"); if (mvno_type != null) { String mvno_match_data = parser.getAttributeValue(null, "mvno_match_data"); if (mvno_match_data != null) { - map.put(Telephony.Carriers.MVNO_TYPE, mvno_type); - map.put(Telephony.Carriers.MVNO_MATCH_DATA, mvno_match_data); + map.put(MVNO_TYPE, mvno_type); + map.put(MVNO_MATCH_DATA, mvno_match_data); } } @@ -1069,9 +1173,9 @@ public class TelephonyProvider extends ContentProvider } static public ContentValues setDefaultValue(ContentValues values) { - if (!values.containsKey(Telephony.Carriers.SUBSCRIPTION_ID)) { - int subId = SubscriptionManager.getDefaultSubId(); - values.put(Telephony.Carriers.SUBSCRIPTION_ID, subId); + if (!values.containsKey(SUBSCRIPTION_ID)) { + int subId = SubscriptionManager.getDefaultSubscriptionId(); + values.put(SUBSCRIPTION_ID, subId); } return values; @@ -1080,8 +1184,7 @@ public class TelephonyProvider extends ContentProvider private void insertAddingDefaults(SQLiteDatabase db, ContentValues row) { row = setDefaultValue(row); try { - db.insertWithOnConflict(CARRIERS_TABLE, null, row, - SQLiteDatabase.CONFLICT_ABORT); + db.insertWithOnConflict(CARRIERS_TABLE, null, row, SQLiteDatabase.CONFLICT_ABORT); if (VDBG) log("dbh.insertAddingDefaults: db.insert returned >= 0; insert " + "successful for cv " + row); } catch (SQLException e) { @@ -1095,20 +1198,19 @@ public class TelephonyProvider extends ContentProvider if (oldRow != null) { // Update the row ContentValues mergedValues = new ContentValues(); - int edited = oldRow.getInt(oldRow.getColumnIndex( - Telephony.Carriers.EDITED)); + int edited = oldRow.getInt(oldRow.getColumnIndex(EDITED)); int old_edited = edited; - if (edited != Telephony.Carriers.UNEDITED) { - if (edited == Telephony.Carriers.USER_DELETED) { + if (edited != UNEDITED) { + if (edited == USER_DELETED) { // USER_DELETED_BUT_PRESENT_IN_XML indicates entry has been deleted // by user but present in apn xml file. - edited = Telephony.Carriers.USER_DELETED_BUT_PRESENT_IN_XML; - } else if (edited == Telephony.Carriers.CARRIER_DELETED) { + edited = USER_DELETED_BUT_PRESENT_IN_XML; + } else if (edited == CARRIER_DELETED) { // CARRIER_DELETED_BUT_PRESENT_IN_XML indicates entry has been deleted // by user but present in apn xml file. - edited = Telephony.Carriers.CARRIER_DELETED_BUT_PRESENT_IN_XML; + edited = CARRIER_DELETED_BUT_PRESENT_IN_XML; } - mergedValues.put(Telephony.Carriers.EDITED, edited); + mergedValues.put(EDITED, edited); } mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, row, mergedValues, false, @@ -1125,14 +1227,14 @@ public class TelephonyProvider extends ContentProvider public static void mergeFieldsAndUpdateDb(SQLiteDatabase db, String table, Cursor oldRow, ContentValues newRow, ContentValues mergedValues, boolean onUpgrade, Context context) { - if (newRow.containsKey(Telephony.Carriers.TYPE)) { + if (newRow.containsKey(TYPE)) { // Merge the types - String oldType = oldRow.getString(oldRow.getColumnIndex(Telephony.Carriers.TYPE)); - String newType = newRow.getAsString(Telephony.Carriers.TYPE); + String oldType = oldRow.getString(oldRow.getColumnIndex(TYPE)); + String newType = newRow.getAsString(TYPE); if (!oldType.equalsIgnoreCase(newType)) { if (oldType.equals("") || newType.equals("")) { - newRow.put(Telephony.Carriers.TYPE, ""); + newRow.put(TYPE, ""); } else { String[] oldTypes = oldType.toLowerCase().split(","); String[] newTypes = newType.toLowerCase().split(","); @@ -1140,9 +1242,9 @@ public class TelephonyProvider extends ContentProvider if (VDBG) { log("mergeFieldsAndUpdateDb: Calling separateRowsNeeded() oldType=" + oldType + " old bearer=" + oldRow.getInt(oldRow.getColumnIndex( - Telephony.Carriers.BEARER_BITMASK)) + + BEARER_BITMASK)) + " old profile_id=" + oldRow.getInt(oldRow.getColumnIndex( - Telephony.Carriers.PROFILE_ID)) + + PROFILE_ID)) + " newRow " + newRow); } @@ -1166,26 +1268,24 @@ public class TelephonyProvider extends ContentProvider for (int i = 0; i < mergedTypes.size(); i++) { mergedType.append((i == 0 ? "" : ",") + mergedTypes.get(i)); } - newRow.put(Telephony.Carriers.TYPE, mergedType.toString()); + newRow.put(TYPE, mergedType.toString()); } } - mergedValues.put(Telephony.Carriers.TYPE, newRow.getAsString( - Telephony.Carriers.TYPE)); + mergedValues.put(TYPE, newRow.getAsString( + TYPE)); } - if (newRow.containsKey(Telephony.Carriers.BEARER_BITMASK)) { - int oldBearer = oldRow.getInt(oldRow.getColumnIndex(Telephony.Carriers. - BEARER_BITMASK)); - int newBearer = newRow.getAsInteger(Telephony.Carriers.BEARER_BITMASK); + if (newRow.containsKey(BEARER_BITMASK)) { + int oldBearer = oldRow.getInt(oldRow.getColumnIndex(BEARER_BITMASK)); + int newBearer = newRow.getAsInteger(BEARER_BITMASK); if (oldBearer != newBearer) { if (oldBearer == 0 || newBearer == 0) { - newRow.put(Telephony.Carriers.BEARER_BITMASK, 0); + newRow.put(BEARER_BITMASK, 0); } else { - newRow.put(Telephony.Carriers.BEARER_BITMASK, (oldBearer | newBearer)); + newRow.put(BEARER_BITMASK, (oldBearer | newBearer)); } } - mergedValues.put(Telephony.Carriers.BEARER_BITMASK, newRow.getAsInteger( - Telephony.Carriers.BEARER_BITMASK)); + mergedValues.put(BEARER_BITMASK, newRow.getAsInteger(BEARER_BITMASK)); } if (!onUpgrade) { @@ -1213,8 +1313,7 @@ public class TelephonyProvider extends ContentProvider String[] persistApnsForPlmns = context.getResources().getStringArray( R.array.persist_apns_for_plmn); for (String s : persistApnsForPlmns) { - if (s.equalsIgnoreCase(newRow.getAsString(Telephony.Carriers. - NUMERIC))) { + if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) { match = true; break; } @@ -1225,10 +1324,8 @@ public class TelephonyProvider extends ContentProvider // APN falls under persist_apns_for_plmn // Check if only difference between old type and new type is that // one has dun - ArrayList<String> oldTypesAl = new ArrayList<String>( - Arrays.asList(oldTypes)); - ArrayList<String> newTypesAl = new ArrayList<String>( - Arrays.asList(newTypes)); + ArrayList<String> oldTypesAl = new ArrayList<String>(Arrays.asList(oldTypes)); + ArrayList<String> newTypesAl = new ArrayList<String>(Arrays.asList(newTypes)); ArrayList<String> listWithDun = null; ArrayList<String> listWithoutDun = null; boolean dunInOld = false; @@ -1243,8 +1340,7 @@ public class TelephonyProvider extends ContentProvider return false; } - if (listWithDun.contains("dun") && - !listWithoutDun.contains("dun")) { + if (listWithDun.contains("dun") && !listWithoutDun.contains("dun")) { listWithoutDun.add("dun"); if (!listWithDun.containsAll(listWithoutDun)) { return false; @@ -1253,8 +1349,7 @@ public class TelephonyProvider extends ContentProvider // Only difference between old type and new type is that // one has dun // Check if profile_id is 0/not set - if (oldRow.getInt(oldRow.getColumnIndex(Telephony.Carriers. - PROFILE_ID)) == 0) { + if (oldRow.getInt(oldRow.getColumnIndex(PROFILE_ID)) == 0) { if (dunInOld) { // Update oldRow to remove dun from its type field ContentValues updateOldRow = new ContentValues(); @@ -1268,18 +1363,16 @@ public class TelephonyProvider extends ContentProvider } String updatedType = sb.toString(); if (VDBG) { - log("separateRowsNeeded: updating type in oldRow to " + - updatedType); + log("separateRowsNeeded: updating type in oldRow to " + updatedType); } - updateOldRow.put(Telephony.Carriers.TYPE, updatedType); + updateOldRow.put(TYPE, updatedType); db.update(table, updateOldRow, "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")), null); return true; } else { if (VDBG) log("separateRowsNeeded: adding profile id 1 to newRow"); // Update newRow to set profile_id to 1 - newRow.put(Telephony.Carriers.PROFILE_ID, - new Integer(1)); + newRow.put(PROFILE_ID, new Integer(1)); } } else { return false; @@ -1288,13 +1381,11 @@ public class TelephonyProvider extends ContentProvider // If match was found, both oldRow and newRow need to exist // separately in db. Add newRow to db. try { - db.insertWithOnConflict(table, null, newRow, - SQLiteDatabase.CONFLICT_REPLACE); + db.insertWithOnConflict(table, null, newRow, SQLiteDatabase.CONFLICT_REPLACE); if (VDBG) log("separateRowsNeeded: added newRow with profile id 1 to db"); return true; } catch (SQLException e) { - loge("Exception on trying to add new row after " + - "updating profile_id"); + loge("Exception on trying to add new row after updating profile_id"); } } @@ -1305,50 +1396,37 @@ public class TelephonyProvider extends ContentProvider ContentValues row) { // Conflict is possible only when numeric, mcc, mnc (fields without any default value) // are set in the new row - if (!row.containsKey(Telephony.Carriers.NUMERIC) || - !row.containsKey(Telephony.Carriers.MCC) || - !row.containsKey(Telephony.Carriers.MNC)) { + if (!row.containsKey(NUMERIC) || !row.containsKey(MCC) || !row.containsKey(MNC)) { loge("dbh.selectConflictingRow: called for non-conflicting row: " + row); return null; } String[] columns = { "_id", - Telephony.Carriers.TYPE, - Telephony.Carriers.EDITED, - Telephony.Carriers.BEARER_BITMASK, - Telephony.Carriers.PROFILE_ID }; - String selection = "numeric=? AND mcc=? AND mnc=? AND apn=? AND proxy=? AND port=? " - + "AND mmsproxy=? AND mmsport=? AND mmsc=? AND carrier_enabled=? AND bearer=? " - + "AND mvno_type=? AND mvno_match_data=? AND profile_id=?"; + TYPE, + EDITED, + BEARER_BITMASK, + PROFILE_ID }; + String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?"; int i = 0; String[] selectionArgs = new String[14]; - selectionArgs[i++] = row.getAsString(Telephony.Carriers.NUMERIC); - selectionArgs[i++] = row.getAsString(Telephony.Carriers.MCC); - selectionArgs[i++] = row.getAsString(Telephony.Carriers.MNC); - selectionArgs[i++] = row.containsKey(Telephony.Carriers.APN) ? - row.getAsString(Telephony.Carriers.APN) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.PROXY) ? - row.getAsString(Telephony.Carriers.PROXY) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.PORT) ? - row.getAsString(Telephony.Carriers.PORT) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.MMSPROXY) ? - row.getAsString(Telephony.Carriers.MMSPROXY) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.MMSPORT) ? - row.getAsString(Telephony.Carriers.MMSPORT) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.MMSC) ? - row.getAsString(Telephony.Carriers.MMSC) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.CARRIER_ENABLED) && - (row.getAsString(Telephony.Carriers.CARRIER_ENABLED).equals("0") || - row.getAsString(Telephony.Carriers.CARRIER_ENABLED).equals("false")) ? + selectionArgs[i++] = row.getAsString(NUMERIC); + selectionArgs[i++] = row.getAsString(MCC); + selectionArgs[i++] = row.getAsString(MNC); + selectionArgs[i++] = row.containsKey(APN) ? row.getAsString(APN) : ""; + selectionArgs[i++] = row.containsKey(PROXY) ? row.getAsString(PROXY) : ""; + selectionArgs[i++] = row.containsKey(PORT) ? row.getAsString(PORT) : ""; + selectionArgs[i++] = row.containsKey(MMSPROXY) ? row.getAsString(MMSPROXY) : ""; + selectionArgs[i++] = row.containsKey(MMSPORT) ? row.getAsString(MMSPORT) : ""; + selectionArgs[i++] = row.containsKey(MMSC) ? row.getAsString(MMSC) : ""; + selectionArgs[i++] = row.containsKey(CARRIER_ENABLED) && + (row.getAsString(CARRIER_ENABLED).equals("0") || + row.getAsString(CARRIER_ENABLED).equals("false")) ? "0" : "1"; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.BEARER) ? - row.getAsString(Telephony.Carriers.BEARER) : "0"; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.MVNO_TYPE) ? - row.getAsString(Telephony.Carriers.MVNO_TYPE) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.MVNO_MATCH_DATA) ? - row.getAsString(Telephony.Carriers.MVNO_MATCH_DATA) : ""; - selectionArgs[i++] = row.containsKey(Telephony.Carriers.PROFILE_ID) ? - row.getAsString(Telephony.Carriers.PROFILE_ID) : "0"; + selectionArgs[i++] = row.containsKey(BEARER) ? row.getAsString(BEARER) : "0"; + selectionArgs[i++] = row.containsKey(MVNO_TYPE) ? row.getAsString(MVNO_TYPE) : ""; + selectionArgs[i++] = row.containsKey(MVNO_MATCH_DATA) ? + row.getAsString(MVNO_MATCH_DATA) : ""; + selectionArgs[i++] = row.containsKey(PROFILE_ID) ? row.getAsString(PROFILE_ID) : "0"; Cursor c = db.query(table, columns, selection, selectionArgs, null, null, null); @@ -1400,7 +1478,7 @@ public class TelephonyProvider extends ContentProvider List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList(); for (SubscriptionInfo subInfo : subInfoList) { SharedPreferences spPrefFile = getContext().getSharedPreferences( - PREF_FILE + subInfo.getSubscriptionId(), Context.MODE_PRIVATE); + PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE); if (spPrefFile != null) { SharedPreferences.Editor editor = spPrefFile.edit(); editor.clear(); @@ -1424,7 +1502,8 @@ public class TelephonyProvider extends ContentProvider } private void setPreferredApnId(Long id, int subId) { - SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN, + Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putLong(COLUMN_APN_ID + subId, id != null ? id.longValue() : INVALID_APN_ID); editor.apply(); @@ -1435,7 +1514,8 @@ public class TelephonyProvider extends ContentProvider } private long getPreferredApnId(int subId, boolean checkApnSp) { - SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN, + Context.MODE_PRIVATE); long apnId = sp.getLong(COLUMN_APN_ID + subId, INVALID_APN_ID); if (apnId == INVALID_APN_ID && checkApnSp) { apnId = getPreferredApnIdFromApn(subId); @@ -1448,7 +1528,8 @@ public class TelephonyProvider extends ContentProvider } private void deletePreferredApnId() { - SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN, + Context.MODE_PRIVATE); // before deleting, save actual preferred apns (not the ids) in a separate SP Map<String, ?> allPrefApnId = sp.getAll(); for (String key : allPrefApnId.keySet()) { @@ -1512,7 +1593,8 @@ public class TelephonyProvider extends ContentProvider } i++; } - Cursor c = db.query(CARRIERS_TABLE, new String[]{"_id"}, where, whereArgs, null, null, null); + Cursor c = db.query(CARRIERS_TABLE, new String[]{"_id"}, where, whereArgs, null, null, + null); if (c != null) { if (c.getCount() == 1) { c.moveToFirst(); @@ -1551,7 +1633,7 @@ public class TelephonyProvider extends ContentProvider + selection + "selectionArgs=" + selectionArgs + ", sort=" + sort); TelephonyManager mTelephonyManager = (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE); - int subId = SubscriptionManager.getDefaultSubId(); + int subId = SubscriptionManager.getDefaultSubscriptionId(); String subIdString; SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setStrict(true); // a little protection from injection attacks @@ -1568,7 +1650,7 @@ public class TelephonyProvider extends ContentProvider return null; } if (DBG) log("subIdString = " + subIdString + " subId = " + subId); - qb.appendWhere("numeric = '" + mTelephonyManager.getSimOperator(subId)+"'"); + qb.appendWhere(NUMERIC + " = '" + mTelephonyManager.getSimOperator(subId) + "'"); // FIXME alter the selection to pass subId // selection = selection + "and subId = " } @@ -1634,11 +1716,11 @@ public class TelephonyProvider extends ContentProvider if (match != URL_SIMINFO) { if (projectionIn != null) { for (String column : projectionIn) { - if (Telephony.Carriers.TYPE.equals(column) || - Telephony.Carriers.MMSC.equals(column) || - Telephony.Carriers.MMSPROXY.equals(column) || - Telephony.Carriers.MMSPORT.equals(column) || - Telephony.Carriers.APN.equals(column)) { + if (TYPE.equals(column) || + MMSC.equals(column) || + MMSPROXY.equals(column) || + MMSPORT.equals(column) || + APN.equals(column)) { // noop } else { checkPermission(); @@ -1661,10 +1743,10 @@ public class TelephonyProvider extends ContentProvider } else { selection += " and "; } - selection += "edited!=" + Telephony.Carriers.USER_DELETED + " and edited!=" - + Telephony.Carriers.USER_DELETED_BUT_PRESENT_IN_XML + " and edited!=" - + Telephony.Carriers.CARRIER_DELETED + " and edited!=" - + Telephony.Carriers.CARRIER_DELETED_BUT_PRESENT_IN_XML; + selection += IS_NOT_USER_DELETED + " and " + + IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML + " and " + + IS_NOT_CARRIER_DELETED + " and " + + IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML; if (VDBG) log("query: selection modified to " + selection); } ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort); @@ -1702,7 +1784,7 @@ public class TelephonyProvider extends ContentProvider public synchronized Uri insert(Uri url, ContentValues initialValues) { Uri result = null; - int subId = SubscriptionManager.getDefaultSubId(); + int subId = SubscriptionManager.getDefaultSubscriptionId(); checkPermission(); @@ -1734,18 +1816,18 @@ public class TelephonyProvider extends ContentProvider } values = DatabaseHelper.setDefaultValue(values); - if (!values.containsKey(Telephony.Carriers.EDITED)) { - values.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED); + if (!values.containsKey(EDITED)) { + values.put(EDITED, USER_EDITED); } try { // Replace on conflict so that if same APN is present in db with edited - // as Telephony.Carriers.UNEDITED or USER/CARRIER_DELETED, it is replaced with + // as UNEDITED or USER/CARRIER_DELETED, it is replaced with // edited USER/CARRIER_EDITED long rowID = db.insertWithOnConflict(CARRIERS_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); if (rowID >= 0) { - result = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, rowID); + result = ContentUris.withAppendedId(CONTENT_URI, rowID); notify = true; } if (VDBG) log("insert: inserted " + values.toString() + " rowID = " + rowID); @@ -1783,11 +1865,11 @@ public class TelephonyProvider extends ContentProvider case URL_CURRENT: { // zero out the previous operator - db.update(CARRIERS_TABLE, s_currentNullMap, "current!=0", null); + db.update(CARRIERS_TABLE, s_currentNullMap, CURRENT + "!=0", null); - String numeric = initialValues.getAsString(Telephony.Carriers.NUMERIC); + String numeric = initialValues.getAsString(NUMERIC); int updated = db.update(CARRIERS_TABLE, s_currentSetMap, - "numeric = '" + numeric + "'", null); + NUMERIC + " = '" + numeric + "'", null); if (updated > 0) { @@ -1833,7 +1915,7 @@ public class TelephonyProvider extends ContentProvider } if (notify) { - getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null, + getContext().getContentResolver().notifyChange(CONTENT_URI, null, true, UserHandle.USER_ALL); } @@ -1844,15 +1926,15 @@ public class TelephonyProvider extends ContentProvider public synchronized int delete(Uri url, String where, String[] whereArgs) { int count = 0; - int subId = SubscriptionManager.getDefaultSubId(); + int subId = SubscriptionManager.getDefaultSubscriptionId(); String userOrCarrierEdited = ") and (" + - Telephony.Carriers.EDITED + "=" + Telephony.Carriers.USER_EDITED + " or " + - Telephony.Carriers.EDITED + "=" + Telephony.Carriers.CARRIER_EDITED + ")"; + EDITED + "=" + USER_EDITED + " or " + + EDITED + "=" + CARRIER_EDITED + ")"; String notUserOrCarrierEdited = ") and (" + - Telephony.Carriers.EDITED + "!=" + Telephony.Carriers.USER_EDITED + " and " + - Telephony.Carriers.EDITED + "!=" + Telephony.Carriers.CARRIER_EDITED + ")"; + EDITED + "!=" + USER_EDITED + " and " + + EDITED + "!=" + CARRIER_EDITED + ")"; ContentValues cv = new ContentValues(); - cv.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_DELETED); + cv.put(EDITED, USER_DELETED); checkPermission(); @@ -1911,12 +1993,12 @@ public class TelephonyProvider extends ContentProvider { // Delete user/carrier edited entries count = db.delete(CARRIERS_TABLE, - "(" + Telephony.Carriers._ID + "=?" + userOrCarrierEdited, + "(" + _ID + "=?" + userOrCarrierEdited, new String[] { url.getLastPathSegment() }); // Otherwise mark as user deleted instead of deleting count += db.update(CARRIERS_TABLE, cv, - "(" + Telephony.Carriers._ID + "=?" + notUserOrCarrierEdited, - new String[] { url.getLastPathSegment() }); + "(" + _ID + "=?" + notUserOrCarrierEdited, + new String[]{url.getLastPathSegment() }); break; } @@ -1953,7 +2035,7 @@ public class TelephonyProvider extends ContentProvider case URL_PREFERAPN: case URL_PREFERAPN_NO_UPDATE: { - setPreferredApnId((long)-1, subId); + setPreferredApnId((long)INVALID_APN_ID, subId); if ((match == URL_PREFERAPN) || (match == URL_PREFERAPN_USING_SUBID)) count = 1; break; } @@ -1975,7 +2057,7 @@ public class TelephonyProvider extends ContentProvider } if (count > 0) { - getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null, + getContext().getContentResolver().notifyChange(CONTENT_URI, null, true, UserHandle.USER_ALL); } @@ -1987,7 +2069,7 @@ public class TelephonyProvider extends ContentProvider { int count = 0; int uriType = URL_UNKNOWN; - int subId = SubscriptionManager.getDefaultSubId(); + int subId = SubscriptionManager.getDefaultSubscriptionId(); checkPermission(); @@ -2011,12 +2093,12 @@ public class TelephonyProvider extends ContentProvider case URL_TELEPHONY: { - if (!values.containsKey(Telephony.Carriers.EDITED)) { - values.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED); + if (!values.containsKey(EDITED)) { + values.put(EDITED, USER_EDITED); } // Replace on conflict so that if same APN is present in db with edited - // as Telephony.Carriers.UNEDITED or USER/CARRIER_DELETED, it is replaced with + // as UNEDITED or USER/CARRIER_DELETED, it is replaced with // edited USER/CARRIER_EDITED count = db.updateWithOnConflict(CARRIERS_TABLE, values, where, whereArgs, SQLiteDatabase.CONFLICT_REPLACE); @@ -2039,11 +2121,11 @@ public class TelephonyProvider extends ContentProvider case URL_CURRENT: { - if (!values.containsKey(Telephony.Carriers.EDITED)) { - values.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED); + if (!values.containsKey(EDITED)) { + values.put(EDITED, USER_EDITED); } // Replace on conflict so that if same APN is present in db with edited - // as Telephony.Carriers.UNEDITED or USER/CARRIER_DELETED, it is replaced with + // as UNEDITED or USER/CARRIER_DELETED, it is replaced with // edited USER/CARRIER_EDITED count = db.updateWithOnConflict(CARRIERS_TABLE, values, where, whereArgs, SQLiteDatabase.CONFLICT_REPLACE); @@ -2056,14 +2138,14 @@ public class TelephonyProvider extends ContentProvider throw new UnsupportedOperationException( "Cannot update URL " + url + " with a where clause"); } - if (!values.containsKey(Telephony.Carriers.EDITED)) { - values.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED); + if (!values.containsKey(EDITED)) { + values.put(EDITED, USER_EDITED); } // Replace on conflict so that if same APN is present in db with edited - // as Telephony.Carriers.UNEDITED or USER/CARRIER_DELETED, it is replaced with + // as UNEDITED or USER/CARRIER_DELETED, it is replaced with // edited USER/CARRIER_EDITED count = db.updateWithOnConflict(CARRIERS_TABLE, values, - Telephony.Carriers._ID + "=?", new String[] { url.getLastPathSegment() }, + _ID + "=?", new String[] { url.getLastPathSegment() }, SQLiteDatabase.CONFLICT_REPLACE); break; } @@ -2115,7 +2197,7 @@ public class TelephonyProvider extends ContentProvider break; default: getContext().getContentResolver().notifyChange( - Telephony.Carriers.CONTENT_URI, null, true, UserHandle.USER_ALL); + CONTENT_URI, null, true, UserHandle.USER_ALL); } } @@ -2153,11 +2235,16 @@ public class TelephonyProvider extends ContentProvider } catch (SQLException e) { loge("got exception when deleting to restore: " + e); } - setPreferredApnId((long)-1, subId); + setPreferredApnId((long) INVALID_APN_ID, subId); mOpenHelper.initDatabase(db); } private synchronized void updateApnDb() { + if (!mOpenHelper.apnDbUpdateNeeded()) { + log("Skipping apn db update since apn-conf has not changed."); + return; + } + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); // Delete preferred APN for all subIds @@ -2165,8 +2252,8 @@ public class TelephonyProvider extends ContentProvider // Delete entries in db try { - if (VDBG) log("updateApnDb: deleting edited=Telephony.Carriers.UNEDITED entries"); - db.delete(CARRIERS_TABLE, "edited=" + Telephony.Carriers.UNEDITED, null); + if (VDBG) log("updateApnDb: deleting edited=UNEDITED entries"); + db.delete(CARRIERS_TABLE, IS_UNEDITED, null); } catch (SQLException e) { loge("got exception when deleting to update: " + e); } @@ -2175,7 +2262,7 @@ public class TelephonyProvider extends ContentProvider // Notify listereners of DB change since DB has been updated getContext().getContentResolver().notifyChange( - Telephony.Carriers.CONTENT_URI, null, true, UserHandle.USER_ALL); + CONTENT_URI, null, true, UserHandle.USER_ALL); } |