summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk4
-rw-r--r--AndroidManifest.xml23
-rw-r--r--res/values-b+sr+Latn/config.xml10
-rw-r--r--res/values-b+sr+Latn/strings.xml21
-rw-r--r--res/values-be-rBY/config.xml10
-rw-r--r--res/values-be-rBY/strings.xml21
-rw-r--r--res/values-bs-rBA/config.xml10
-rw-r--r--res/values-bs-rBA/strings.xml21
-rw-r--r--res/values-es/strings.xml2
-rw-r--r--res/values-ro/strings.xml2
-rw-r--r--src/com/android/providers/telephony/MmsProvider.java17
-rw-r--r--src/com/android/providers/telephony/MmsSmsDatabaseHelper.java151
-rw-r--r--src/com/android/providers/telephony/MmsSmsProvider.java3
-rw-r--r--src/com/android/providers/telephony/ProviderUtil.java57
-rw-r--r--src/com/android/providers/telephony/SmsProvider.java118
-rw-r--r--src/com/android/providers/telephony/TelephonyBackupAgent.java1309
-rw-r--r--src/com/android/providers/telephony/TelephonyProvider.java829
-rw-r--r--tests/Android.mk17
-rw-r--r--tests/AndroidManifest.xml28
-rw-r--r--tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java911
20 files changed, 3149 insertions, 415 deletions
diff --git a/Android.mk b/Android.mk
index 7b63072..c824fff 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,7 +5,7 @@ LOCAL_MODULE_TAGS := optional
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_PACKAGE_NAME := TelephonyProvider
LOCAL_CERTIFICATE := platform
@@ -14,3 +14,5 @@ LOCAL_JAVA_LIBRARIES += telephony-common
LOCAL_STATIC_JAVA_LIBRARIES += android-common
include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ff8370d..a2d0bda 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,9 +4,9 @@
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.
@@ -24,12 +24,23 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <protected-broadcast android:name="android.provider.action.EXTERNAL_PROVIDER_CHANGE" />
+ <protected-broadcast android:name="android.intent.action.CONTENT_CHANGED" />
+
+ <!-- This permission is only used to send the ACTION_EXTERNAL_PROVIDER_CHANGE intent. -->
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+
<application android:process="com.android.phone"
android:allowClearUserData="false"
- android:allowBackup="false"
+ android:fullBackupOnly="true"
+ android:backupInForeground="true"
+ android:backupAgent="TelephonyBackupAgent"
+ android:restoreAnyVersion="true"
android:label="@string/app_label"
android:icon="@mipmap/ic_launcher_phone"
- android:usesCleartextTraffic="true">
+ android:usesCleartextTraffic="true"
+ android:defaultToDeviceProtectedStorage="true"
+ android:directBootAware="true">
<provider android:name="TelephonyProvider"
android:authorities="telephony"
@@ -79,5 +90,9 @@
android:singleUser="true"
android:multiprocess="false"
android:writePermission="android.permission.MODIFY_PHONE_STATE" />
+
+ <service
+ android:name=".TelephonyBackupAgent$DeferredSmsMmsRestoreService"
+ android:exported="false" />
</application>
</manifest>
diff --git a/res/values-b+sr+Latn/config.xml b/res/values-b+sr+Latn/config.xml
new file mode 100644
index 0000000..99877a6
--- /dev/null
+++ b/res/values-b+sr+Latn/config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="persist_apns_for_plmn">
+ <item msgid="6413072509259000954">"20404"</item>
+ <item msgid="5639159280778239123">"310004"</item>
+ <item msgid="3860605521380788028">"310120"</item>
+ <item msgid="537693705785480198">"311480"</item>
+ </string-array>
+</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..08aa014
--- /dev/null
+++ b/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2008 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" product="tablet" msgid="9194799012395299737">"Konfiguracija mobilne mreže"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Memorija telefona i poruka"</string>
+</resources>
diff --git a/res/values-be-rBY/config.xml b/res/values-be-rBY/config.xml
new file mode 100644
index 0000000..99877a6
--- /dev/null
+++ b/res/values-be-rBY/config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="persist_apns_for_plmn">
+ <item msgid="6413072509259000954">"20404"</item>
+ <item msgid="5639159280778239123">"310004"</item>
+ <item msgid="3860605521380788028">"310120"</item>
+ <item msgid="537693705785480198">"311480"</item>
+ </string-array>
+</resources>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..3c86bf4
--- /dev/null
+++ b/res/values-be-rBY/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2008 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" product="tablet" msgid="9194799012395299737">"Налады мабiльнай сеткi"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Сховішча для тэлефаніі і паведамленняў"</string>
+</resources>
diff --git a/res/values-bs-rBA/config.xml b/res/values-bs-rBA/config.xml
new file mode 100644
index 0000000..99877a6
--- /dev/null
+++ b/res/values-bs-rBA/config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="persist_apns_for_plmn">
+ <item msgid="6413072509259000954">"20404"</item>
+ <item msgid="5639159280778239123">"310004"</item>
+ <item msgid="3860605521380788028">"310120"</item>
+ <item msgid="537693705785480198">"311480"</item>
+ </string-array>
+</resources>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..9d19176
--- /dev/null
+++ b/res/values-bs-rBA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2008 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" product="tablet" msgid="9194799012395299737">"Konfiguracija mobilne mreže"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Pohrana za telefon i poruke"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 865f254..cb697b4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" product="tablet" msgid="9194799012395299737">"Configuración de red móvil"</string>
- <string name="app_label" product="default" msgid="8338087656149558019">"Almac. Mensajes/Teléfono"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Almacenamiento de mensajes y teléfono"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index a2c590c..39fa7b9 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -16,6 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" product="tablet" msgid="9194799012395299737">"Configurația reţelei de telefonie mobilă"</string>
+ <string name="app_label" product="tablet" msgid="9194799012395299737">"Configurația rețelei de telefonie mobilă"</string>
<string name="app_label" product="default" msgid="8338087656149558019">"Stocare Telefon/Mesagerie"</string>
</resources>
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);
}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..fc12378
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+LOCAL_PACKAGE_NAME := TelephonyProviderTests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_INSTRUMENTATION_FOR := TelephonyProvider
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..7a273fc
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.telephony.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.providers.telephony"
+ android:label="Tests for TelephonyProvider">
+ </instrumentation>
+</manifest>
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
new file mode 100644
index 0000000..4866dbd
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -0,0 +1,911 @@
+/*
+ * 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 android.annotation.TargetApi;
+import android.app.backup.FullBackupDataOutput;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.ContextWrapper;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.BaseColumns;
+import android.provider.Telephony;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockCursor;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.SparseArray;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+
+/**
+ * Tests for testing backup/restore of SMS and text MMS messages.
+ * For backup it creates fake provider and checks resulting json array.
+ * For restore provides json array and checks inserts of the messages into provider.
+ */
+@TargetApi(Build.VERSION_CODES.M)
+public class TelephonyBackupAgentTest extends AndroidTestCase {
+ /* Map subscriptionId -> phone number */
+ private SparseArray<String> mSubId2Phone;
+ /* Map phone number -> subscriptionId */
+ private ArrayMap<String, Integer> mPhone2SubId;
+ /* Table being used for sms cursor */
+ private final List<ContentValues> mSmsTable = new ArrayList<>();
+ /* Table begin used for mms cursor */
+ private final List<ContentValues> mMmsTable = new ArrayList<>();
+ /* Table contains parts, addresses of mms */
+ private final List<ContentValues> mMmsAllContentValues = new ArrayList<>();
+ /* Cursors being used to access sms, mms tables */
+ private FakeCursor mSmsCursor, mMmsCursor;
+ /* Test data with sms and mms */
+ private ContentValues[] mSmsRows, mMmsRows;
+ /* Json representation for the test data */
+ private String[] mSmsJson, mMmsJson;
+ /* sms, mms json concatenated as json array */
+ private String mAllSmsJson, mAllMmsJson;
+
+ private StringWriter mStringWriter;
+
+ /* Content resolver passed to the backupAgent */
+ private MockContentResolver mMockContentResolver = new MockContentResolver();
+
+ /* Map uri -> cursors. Being used for contentprovider. */
+ private Map<Uri, FakeCursor> mCursors;
+ /* Content provider with threadIds.*/
+ private ThreadProvider mThreadProvider = new ThreadProvider();
+
+ private static final String EMPTY_JSON_ARRAY = "[]";
+
+ TelephonyBackupAgent mTelephonyBackupAgent;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ /* Filling up subscription maps */
+ mStringWriter = new StringWriter();
+ mSubId2Phone = new SparseArray<String>();
+ mSubId2Phone.append(1, "+111111111111111");
+ mSubId2Phone.append(3, "+333333333333333");
+
+ mPhone2SubId = new ArrayMap<>();
+ for (int i=0; i<mSubId2Phone.size(); ++i) {
+ mPhone2SubId.put(mSubId2Phone.valueAt(i), mSubId2Phone.keyAt(i));
+ }
+
+ mCursors = new HashMap<Uri, FakeCursor>();
+ /* Bind tables to the cursors */
+ mSmsCursor = new FakeCursor(mSmsTable, TelephonyBackupAgent.SMS_PROJECTION);
+ mCursors.put(Telephony.Sms.CONTENT_URI, mSmsCursor);
+ mMmsCursor = new FakeCursor(mMmsTable, TelephonyBackupAgent.MMS_PROJECTION);
+ mCursors.put(Telephony.Mms.CONTENT_URI, mMmsCursor);
+
+
+ /* Generating test data */
+ mSmsRows = new ContentValues[4];
+ mSmsJson = new String[4];
+ mSmsRows[0] = createSmsRow(1, 1, "+1232132214124", "sms 1", "sms subject", 9087978987l,
+ 999999999, 3, 44, 1);
+ mSmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"address\":" +
+ "\"+1232132214124\",\"body\":\"sms 1\",\"subject\":\"sms subject\",\"date\":" +
+ "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"3\",\"type\":\"44\"," +
+ "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true}";
+ mThreadProvider.setArchived(
+ mThreadProvider.getOrCreateThreadId(new String[]{"+123 (213) 2214124"}));
+
+ mSmsRows[1] = createSmsRow(2, 2, "+1232132214124", "sms 2", null, 9087978987l, 999999999,
+ 0, 4, 1);
+ mSmsJson[1] = "{\"address\":\"+1232132214124\",\"body\":\"sms 2\",\"date\":" +
+ "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"0\",\"type\":\"4\"," +
+ "\"recipients\":[\"+123 (213) 2214124\"]}";
+
+ mSmsRows[2] = createSmsRow(4, 3, "+1232221412433 +1232221412444", "sms 3", null,
+ 111111111111l, 999999999, 2, 3, 2);
+ mSmsJson[2] = "{\"self_phone\":\"+333333333333333\",\"address\":" +
+ "\"+1232221412433 +1232221412444\",\"body\":\"sms 3\",\"date\":\"111111111111\"," +
+ "\"date_sent\":" +
+ "\"999999999\",\"status\":\"2\",\"type\":\"3\"," +
+ "\"recipients\":[\"+1232221412433\",\"+1232221412444\"]}";
+ mThreadProvider.getOrCreateThreadId(new String[]{"+1232221412433", "+1232221412444"});
+
+
+ mSmsRows[3] = createSmsRow(5, 3, null, "sms 4", null,
+ 111111111111l, 999999999, 2, 3, 5);
+ mSmsJson[3] = "{\"self_phone\":\"+333333333333333\"," +
+ "\"body\":\"sms 4\",\"date\":\"111111111111\"," +
+ "\"date_sent\":" +
+ "\"999999999\",\"status\":\"2\",\"type\":\"3\"}";
+
+ mAllSmsJson = makeJsonArray(mSmsJson);
+
+
+
+ mMmsRows = new ContentValues[3];
+ mMmsJson = new String[3];
+ mMmsRows[0] = createMmsRow(1 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
+ 100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
+ 17 /*version*/, 1 /*textonly*/,
+ 11 /*msgBox*/, "location 1" /*contentLocation*/, "MMs body 1" /*body*/,
+ 111 /*body charset*/,
+ new String[]{"+111 (111) 11111111", "+11121212", "example@example.com",
+ "+999999999"} /*addresses*/,
+ 3 /*threadId*/);
+
+ mMmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
+ "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
+ "\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
+ "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
+ "\"mms_addresses\":" +
+ "[{\"type\":10,\"address\":\"+111 (111) 11111111\",\"charset\":100}," +
+ "{\"type\":11,\"address\":\"+11121212\",\"charset\":101},{\"type\":12,\"address\":"+
+ "\"example@example.com\",\"charset\":102},{\"type\":13,\"address\":\"+999999999\"" +
+ ",\"charset\":103}],\"mms_body\":\"MMs body 1\",\"mms_charset\":111,\"" +
+ "sub_cs\":\"100\"}";
+ mThreadProvider.getOrCreateThreadId(new String[]{"+11121212", "example@example.com",
+ "+999999999"});
+
+ mMmsRows[1] = createMmsRow(2 /*id*/, 2 /*subid*/, null /*subject*/, 100 /*subcharset*/,
+ 111122 /*date*/, 1111112 /*datesent*/, 4 /*type*/, 18 /*version*/, 1 /*textonly*/,
+ 222 /*msgBox*/, "location 2" /*contentLocation*/, "MMs body 2" /*body*/,
+ 121 /*body charset*/,
+ new String[]{"+7 (333) ", "example@example.com", "+999999999"} /*addresses*/,
+ 4 /*threadId*/);
+ mMmsJson[1] = "{\"date\":\"111122\",\"date_sent\":\"1111112\",\"m_type\":\"4\"," +
+ "\"v\":\"18\",\"msg_box\":\"222\",\"ct_l\":\"location 2\"," +
+ "\"recipients\":[\"example@example.com\",\"+999999999\"]," +
+ "\"mms_addresses\":" +
+ "[{\"type\":10,\"address\":\"+7 (333) \",\"charset\":100}," +
+ "{\"type\":11,\"address\":\"example@example.com\",\"charset\":101}," +
+ "{\"type\":12,\"address\":\"+999999999\",\"charset\":102}]," +
+ "\"mms_body\":\"MMs body 2\",\"mms_charset\":121}";
+ mThreadProvider.getOrCreateThreadId(new String[]{"example@example.com", "+999999999"});
+
+ mMmsRows[2] = createMmsRow(9 /*id*/, 3 /*subid*/, "Subject 10" /*subject*/,
+ 10 /*subcharset*/, 111133 /*date*/, 1111132 /*datesent*/, 5 /*type*/,
+ 19 /*version*/, 1 /*textonly*/,
+ 333 /*msgBox*/, null /*contentLocation*/, "MMs body 3" /*body*/,
+ 131 /*body charset*/,
+ new String[]{"333 333333333333", "+1232132214124"} /*addresses*/,
+ 1 /*threadId*/);
+
+ mMmsJson[2] = "{\"self_phone\":\"+333333333333333\",\"sub\":\"Subject 10\"," +
+ "\"date\":\"111133\",\"date_sent\":\"1111132\",\"m_type\":\"5\",\"v\":\"19\"," +
+ "\"msg_box\":\"333\"," +
+ "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true," +
+ "\"mms_addresses\":" +
+ "[{\"type\":10,\"address\":\"333 333333333333\",\"charset\":100}," +
+ "{\"type\":11,\"address\":\"+1232132214124\",\"charset\":101}]," +
+ "\"mms_body\":\"MMs body 3\",\"mms_charset\":131," +
+ "\"sub_cs\":\"10\"}";
+ mAllMmsJson = makeJsonArray(mMmsJson);
+
+ ContentProvider contentProvider = new MockContentProvider() {
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (mCursors.containsKey(uri)) {
+ FakeCursor fakeCursor = mCursors.get(uri);
+ if (projection != null) {
+ fakeCursor.setProjection(projection);
+ }
+ fakeCursor.nextRow = 0;
+ return fakeCursor;
+ }
+ fail("No cursor for " + uri.toString());
+ return null;
+ }
+ };
+
+ mMockContentResolver.addProvider("sms", contentProvider);
+ mMockContentResolver.addProvider("mms", contentProvider);
+ mMockContentResolver.addProvider("mms-sms", mThreadProvider);
+
+ mTelephonyBackupAgent = new TelephonyBackupAgent();
+ mTelephonyBackupAgent.attach(new ContextWrapper(getContext()) {
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockContentResolver;
+ }
+ });
+
+
+ mTelephonyBackupAgent.clearSharedPreferences();
+ mTelephonyBackupAgent.setContentResolver(mMockContentResolver);
+ mTelephonyBackupAgent.setSubId(mSubId2Phone, mPhone2SubId);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mTelephonyBackupAgent.clearSharedPreferences();
+ super.tearDown();
+ }
+
+ private static String makeJsonArray(String[] json) {
+ StringBuilder stringBuilder = new StringBuilder("[");
+ for (int i=0; i<json.length; ++i) {
+ if (i > 0) {
+ stringBuilder.append(",");
+ }
+ stringBuilder.append(json[i]);
+ }
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ private static ContentValues createSmsRow(int id, int subId, String address, String body,
+ String subj, long date, long dateSent,
+ int status, int type, long threadId) {
+ ContentValues smsRow = new ContentValues();
+ smsRow.put(Telephony.Sms._ID, id);
+ smsRow.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
+ if (address != null) {
+ smsRow.put(Telephony.Sms.ADDRESS, address);
+ }
+ if (body != null) {
+ smsRow.put(Telephony.Sms.BODY, body);
+ }
+ if (subj != null) {
+ smsRow.put(Telephony.Sms.SUBJECT, subj);
+ }
+ smsRow.put(Telephony.Sms.DATE, String.valueOf(date));
+ smsRow.put(Telephony.Sms.DATE_SENT, String.valueOf(dateSent));
+ smsRow.put(Telephony.Sms.STATUS, String.valueOf(status));
+ smsRow.put(Telephony.Sms.TYPE, String.valueOf(type));
+ smsRow.put(Telephony.Sms.THREAD_ID, threadId);
+
+ return smsRow;
+ }
+
+ private ContentValues createMmsRow(int id, int subId, String subj, int subCharset,
+ long date, long dateSent, int type, int version,
+ int textOnly, int msgBox,
+ String contentLocation, String body,
+ int bodyCharset, String[] addresses, long threadId) {
+ ContentValues mmsRow = new ContentValues();
+ mmsRow.put(Telephony.Mms._ID, id);
+ mmsRow.put(Telephony.Mms.SUBSCRIPTION_ID, subId);
+ if (subj != null) {
+ mmsRow.put(Telephony.Mms.SUBJECT, subj);
+ mmsRow.put(Telephony.Mms.SUBJECT_CHARSET, String.valueOf(subCharset));
+ }
+ mmsRow.put(Telephony.Mms.DATE, String.valueOf(date));
+ mmsRow.put(Telephony.Mms.DATE_SENT, String.valueOf(dateSent));
+ mmsRow.put(Telephony.Mms.MESSAGE_TYPE, String.valueOf(type));
+ mmsRow.put(Telephony.Mms.MMS_VERSION, String.valueOf(version));
+ mmsRow.put(Telephony.Mms.TEXT_ONLY, textOnly);
+ mmsRow.put(Telephony.Mms.MESSAGE_BOX, String.valueOf(msgBox));
+ if (contentLocation != null) {
+ mmsRow.put(Telephony.Mms.CONTENT_LOCATION, contentLocation);
+ }
+ mmsRow.put(Telephony.Mms.THREAD_ID, threadId);
+
+ final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
+ appendPath("part").build();
+ mCursors.put(partUri, createBodyCursor(body, bodyCharset));
+ mMmsAllContentValues.add(mmsRow);
+
+ final Uri addrUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
+ appendPath("addr").build();
+ mCursors.put(addrUri, createAddrCursor(addresses));
+
+ return mmsRow;
+ }
+
+ private static final String APP_SMIL = "application/smil";
+ private static final String TEXT_PLAIN = "text/plain";
+
+ // Cursor with parts of Mms.
+ private FakeCursor createBodyCursor(String body, int charset) {
+ List<ContentValues> table = new ArrayList<>();
+ final String srcName = String.format("text.%06d.txt", 0);
+ final String smilBody = String.format(TelephonyBackupAgent.sSmilTextPart, srcName);
+ final String smil = String.format(TelephonyBackupAgent.sSmilTextOnly, smilBody);
+
+ final ContentValues smilPart = new ContentValues();
+ smilPart.put(Telephony.Mms.Part.SEQ, -1);
+ smilPart.put(Telephony.Mms.Part.CONTENT_TYPE, APP_SMIL);
+ smilPart.put(Telephony.Mms.Part.NAME, "smil.xml");
+ smilPart.put(Telephony.Mms.Part.CONTENT_ID, "<smil>");
+ smilPart.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml");
+ smilPart.put(Telephony.Mms.Part.TEXT, smil);
+ mMmsAllContentValues.add(smilPart);
+
+ final ContentValues bodyPart = new ContentValues();
+ bodyPart.put(Telephony.Mms.Part.SEQ, 0);
+ bodyPart.put(Telephony.Mms.Part.CONTENT_TYPE, TEXT_PLAIN);
+ bodyPart.put(Telephony.Mms.Part.NAME, srcName);
+ bodyPart.put(Telephony.Mms.Part.CONTENT_ID, "<"+srcName+">");
+ bodyPart.put(Telephony.Mms.Part.CONTENT_LOCATION, srcName);
+ bodyPart.put(Telephony.Mms.Part.CHARSET, charset);
+ bodyPart.put(Telephony.Mms.Part.TEXT, body);
+ table.add(bodyPart);
+ mMmsAllContentValues.add(bodyPart);
+
+ return new FakeCursor(table, TelephonyBackupAgent.MMS_TEXT_PROJECTION);
+ }
+
+ // Cursor with addresses of Mms.
+ private FakeCursor createAddrCursor(String[] addresses) {
+ List<ContentValues> table = new ArrayList<>();
+ for (int i=0; i<addresses.length; ++i) {
+ ContentValues addr = new ContentValues();
+ addr.put(Telephony.Mms.Addr.TYPE, 10+i);
+ addr.put(Telephony.Mms.Addr.ADDRESS, addresses[i]);
+ addr.put(Telephony.Mms.Addr.CHARSET, 100+i);
+ mMmsAllContentValues.add(addr);
+ table.add(addr);
+ }
+ return new FakeCursor(table, TelephonyBackupAgent.MMS_ADDR_PROJECTION);
+ }
+
+ /**
+ * Test with no sms in the provider.
+ * @throws Exception
+ */
+ public void testBackupSms_NoSms() throws Exception {
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
+ }
+
+ /**
+ * Test with 3 sms in the provider with the limit per file 4.
+ * @throws Exception
+ */
+ public void testBackupSms_AllSms() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 4;
+ mSmsTable.addAll(Arrays.asList(mSmsRows));
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(mAllSmsJson, mStringWriter.toString());
+ }
+
+ /**
+ * Test with 3 sms in the provider with the limit per file 3.
+ * @throws Exception
+ */
+ public void testBackupSms_AllSmsWithExactFileLimit() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 4;
+ mSmsTable.addAll(Arrays.asList(mSmsRows));
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(mAllSmsJson, mStringWriter.toString());
+ }
+
+ /**
+ * Test with 3 sms in the provider with the limit per file 1.
+ * @throws Exception
+ */
+ public void testBackupSms_AllSmsOneMessagePerFile() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 1;
+ mSmsTable.addAll(Arrays.asList(mSmsRows));
+
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mSmsJson[0] + "]", mStringWriter.toString());
+
+ mStringWriter = new StringWriter();
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mSmsJson[1] + "]", mStringWriter.toString());
+
+ mStringWriter = new StringWriter();
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mSmsJson[2] + "]", mStringWriter.toString());
+
+ mStringWriter = new StringWriter();
+ mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mSmsJson[3] + "]", mStringWriter.toString());
+ }
+
+ /**
+ * Test with no mms in the pvovider.
+ * @throws Exception
+ */
+ public void testBackupMms_NoMms() throws Exception {
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
+ }
+
+ /**
+ * Test with all mms.
+ * @throws Exception
+ */
+ public void testBackupMms_AllMms() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 4;
+ mMmsTable.addAll(Arrays.asList(mMmsRows));
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(mAllMmsJson, mStringWriter.toString());
+ }
+
+ /**
+ * Test with 3 mms in the provider with the limit per file 1.
+ * @throws Exception
+ */
+ public void testBackupMms_OneMessagePerFile() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 1;
+ mMmsTable.addAll(Arrays.asList(mMmsRows));
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mMmsJson[0] + "]", mStringWriter.toString());
+
+ mStringWriter = new StringWriter();
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mMmsJson[1] + "]", mStringWriter.toString());
+
+ mStringWriter = new StringWriter();
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals("[" + mMmsJson[2] + "]", mStringWriter.toString());
+ }
+
+ /**
+ * Test with 3 mms in the provider with the limit per file 3.
+ * @throws Exception
+ */
+ public void testBackupMms_WithExactFileLimit() throws Exception {
+ mMmsTable.addAll(Arrays.asList(mMmsRows));
+ mTelephonyBackupAgent.mMaxMsgPerFile = 3;
+ mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
+ assertEquals(mAllMmsJson, mStringWriter.toString());
+ }
+
+ /**
+ * Test restore sms with the empty json array "[]".
+ * @throws Exception
+ */
+ public void testRestoreSms_NoSms() throws Exception {
+ JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
+ FakeSmsProvider smsProvider = new FakeSmsProvider(null);
+ mMockContentResolver.addProvider("sms", smsProvider);
+ mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
+ assertEquals(0, smsProvider.getRowsAdded());
+ }
+
+ /**
+ * Test restore sms with three sms json object in the array.
+ * @throws Exception
+ */
+ public void testRestoreSms_AllSms() throws Exception {
+ mTelephonyBackupAgent.initUnknownSender();
+ JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
+ FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows);
+ mMockContentResolver.addProvider("sms", smsProvider);
+ mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
+ assertEquals(mSmsRows.length, smsProvider.getRowsAdded());
+ assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
+ }
+
+ /**
+ * Test restore mms with the empty json array "[]".
+ * @throws Exception
+ */
+ public void testRestoreMms_NoMms() throws Exception {
+ JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
+ FakeMmsProvider mmsProvider = new FakeMmsProvider(null);
+ mMockContentResolver.addProvider("mms", mmsProvider);
+ mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ assertEquals(0, mmsProvider.getRowsAdded());
+ }
+
+ /**
+ * Test restore sms with three mms json object in the array.
+ * @throws Exception
+ */
+ public void testRestoreMms_AllMms() throws Exception {
+ JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllMmsJson)));
+ FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
+ mMockContentResolver.addProvider("mms", mmsProvider);
+ mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+ assertEquals(18, mmsProvider.getRowsAdded());
+ assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
+ }
+
+ /**
+ * Test with quota exceeded. Checking size of the backup before it hits quota and after.
+ * It still backs up more than a quota since there is meta-info which matters with small amounts
+ * of data. The agent does not take backup meta-info into consideration.
+ * @throws Exception
+ */
+ public void testBackup_WithQuotaExceeded() throws Exception {
+ mTelephonyBackupAgent.mMaxMsgPerFile = 1;
+ final int backupSize = 7168;
+ final int backupSizeAfterFirstQuotaHit = 6144;
+ final int backupSizeAfterSecondQuotaHit = 5120;
+
+ mSmsTable.addAll(Arrays.asList(mSmsRows));
+ mMmsTable.addAll(Arrays.asList(mMmsRows));
+
+ FullBackupDataOutput fullBackupDataOutput = new FullBackupDataOutput();
+ mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
+ assertEquals(backupSize, fullBackupDataOutput.getSize());
+
+ mTelephonyBackupAgent.onQuotaExceeded(backupSize, backupSize - 100);
+ fullBackupDataOutput = new FullBackupDataOutput();
+ mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
+ assertEquals(backupSizeAfterFirstQuotaHit, fullBackupDataOutput.getSize());
+
+ mTelephonyBackupAgent.onQuotaExceeded(backupSizeAfterFirstQuotaHit,
+ backupSizeAfterFirstQuotaHit - 200);
+ fullBackupDataOutput = new FullBackupDataOutput();
+ mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
+ assertEquals(backupSizeAfterSecondQuotaHit, fullBackupDataOutput.getSize());
+ }
+
+ // Adding random keys to JSON to test handling it by the BackupAgent on restore.
+ private String addRandomDataToJson(String jsonString) throws JSONException {
+ JSONArray jsonArray = new JSONArray(jsonString);
+ JSONArray res = new JSONArray();
+ for (int i = 0; i < jsonArray.length(); ++i) {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ jsonObject.put(UUID.randomUUID().toString(), UUID.randomUUID().toString());
+ res = res.put(jsonObject);
+ }
+ return res.toString();
+ }
+
+ /**
+ * class for checking sms insertion into the provider on restore.
+ */
+ private class FakeSmsProvider extends MockContentProvider {
+ private int nextRow = 0;
+ private ContentValues[] mSms;
+
+ public FakeSmsProvider(ContentValues[] sms) {
+ this.mSms = sms;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(Telephony.Sms.CONTENT_URI, uri);
+ ContentValues modifiedValues = new ContentValues(mSms[nextRow++]);
+ modifiedValues.remove(Telephony.Sms._ID);
+ modifiedValues.put(Telephony.Sms.READ, 1);
+ modifiedValues.put(Telephony.Sms.SEEN, 1);
+ if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
+ == null) {
+ modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
+ }
+
+ if (modifiedValues.get(Telephony.Sms.ADDRESS) == null) {
+ modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
+ }
+
+ assertEquals(modifiedValues, values);
+ return null;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ for (ContentValues cv : values) {
+ insert(uri, cv);
+ }
+ return values.length;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ public int getRowsAdded() {
+ return nextRow;
+ }
+ }
+
+ /**
+ * class for checking mms insertion into the provider on restore.
+ */
+ private class FakeMmsProvider extends MockContentProvider {
+ private int nextRow = 0;
+ private List<ContentValues> mValues;
+ private long mDummyMsgId = -1;
+ private long mMsgId = -1;
+
+ public FakeMmsProvider(List<ContentValues> values) {
+ this.mValues = values;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Uri retUri = Uri.parse("dummy_uri");
+ ContentValues modifiedValues = new ContentValues(mValues.get(nextRow++));
+ if (APP_SMIL.equals(values.get(Telephony.Mms.Part.CONTENT_TYPE))) {
+ // Smil part.
+ assertEquals(-1, mDummyMsgId);
+ mDummyMsgId = values.getAsLong(Telephony.Mms.Part.MSG_ID);
+ }
+
+ if (values.get(Telephony.Mms.Part.SEQ) != null) {
+ // Part of mms.
+ final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(mDummyMsgId))
+ .appendPath("part")
+ .build();
+ assertEquals(expectedUri, uri);
+ }
+
+ if (values.get(Telephony.Mms.Part.MSG_ID) != null) {
+ modifiedValues.put(Telephony.Mms.Part.MSG_ID, mDummyMsgId);
+ }
+
+
+ if (values.get(Telephony.Mms.SUBSCRIPTION_ID) != null) {
+ assertEquals(Telephony.Mms.CONTENT_URI, uri);
+ if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
+ == null) {
+ modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
+ }
+ // Mms.
+ modifiedValues.put(Telephony.Mms.READ, 1);
+ modifiedValues.put(Telephony.Mms.SEEN, 1);
+ mMsgId = modifiedValues.getAsInteger(BaseColumns._ID);
+ retUri = Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, String.valueOf(mMsgId));
+ modifiedValues.remove(BaseColumns._ID);
+ }
+
+ if (values.get(Telephony.Mms.Addr.ADDRESS) != null) {
+ // Address.
+ final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(mMsgId))
+ .appendPath("addr")
+ .build();
+ assertEquals(expectedUri, uri);
+ assertNotSame(-1, mMsgId);
+ modifiedValues.put(Telephony.Mms.Addr.MSG_ID, mMsgId);
+ mDummyMsgId = -1;
+ }
+
+ for (String key : modifiedValues.keySet()) {
+ assertEquals("Key:"+key, modifiedValues.get(key), values.get(key));
+ }
+ assertEquals(modifiedValues.size(), values.size());
+ return retUri;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(mDummyMsgId))
+ .appendPath("part")
+ .build();
+ assertEquals(expectedUri, uri);
+ ContentValues expected = new ContentValues();
+ expected.put(Telephony.Mms.Part.MSG_ID, mMsgId);
+ assertEquals(expected, values);
+ return 2;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ public int getRowsAdded() {
+ return nextRow;
+ }
+ }
+
+ /**
+ * class that implements MmsSms provider for thread ids.
+ */
+ private static class ThreadProvider extends MockContentProvider {
+ ArrayList<Set<Integer> > id2Thread = new ArrayList<>();
+ ArrayList<String> id2Recipient = new ArrayList<>();
+ Set<Integer> mIsThreadArchived = new HashSet<>();
+ Set<Integer> mUpdateThreadsArchived = new HashSet<>();
+
+
+ public int getOrCreateThreadId(final String[] recipients) {
+ if (recipients == null || recipients.length == 0) {
+ throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+ }
+
+ Set<Integer> ids = new ArraySet<>();
+ for (String rec : recipients) {
+ if (!id2Recipient.contains(rec)) {
+ id2Recipient.add(rec);
+ }
+ ids.add(id2Recipient.indexOf(rec)+1);
+ }
+ if (!id2Thread.contains(ids)) {
+ id2Thread.add(ids);
+ }
+ return id2Thread.indexOf(ids)+1;
+ }
+
+ public void setArchived(int threadId) {
+ mIsThreadArchived.add(threadId);
+ }
+
+ private String getSpaceSepIds(int threadId) {
+ if (id2Thread.size() < threadId) {
+ return null;
+ }
+
+ String spaceSepIds = null;
+ for (Integer id : id2Thread.get(threadId-1)) {
+ spaceSepIds = (spaceSepIds == null ? "" : spaceSepIds + " ") + String.valueOf(id);
+ }
+ return spaceSepIds;
+ }
+
+ private String getRecipient(int recipientId) {
+ return id2Recipient.get(recipientId-1);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ if (uri.equals(TelephonyBackupAgent.ALL_THREADS_URI)) {
+ final int threadId = Integer.parseInt(selectionArgs[0]);
+ final String spaceSepIds = getSpaceSepIds(threadId);
+ List<ContentValues> table = new ArrayList<>();
+ ContentValues row = new ContentValues();
+ row.put(Telephony.Threads.RECIPIENT_IDS, spaceSepIds);
+ table.add(row);
+ return new FakeCursor(table, projection);
+ } else if (uri.toString().startsWith(Telephony.Threads.CONTENT_URI.toString())) {
+ assertEquals(1, projection.length);
+ assertEquals(Telephony.Threads.ARCHIVED, projection[0]);
+ List<String> segments = uri.getPathSegments();
+ final int threadId = Integer.parseInt(segments.get(segments.size() - 2));
+ List<ContentValues> table = new ArrayList<>();
+ ContentValues row = new ContentValues();
+ row.put(Telephony.Threads.ARCHIVED, mIsThreadArchived.contains(threadId) ? 1 : 0);
+ table.add(row);
+ return new FakeCursor(table, projection);
+ } else if (uri.toString().startsWith(
+ TelephonyBackupAgent.SINGLE_CANONICAL_ADDRESS_URI.toString())) {
+ final int recipientId = (int)ContentUris.parseId(uri);
+ final String recipient = getRecipient(recipientId);
+ List<ContentValues> table = new ArrayList<>();
+ ContentValues row = new ContentValues();
+ row.put(Telephony.CanonicalAddressesColumns.ADDRESS, recipient);
+ table.add(row);
+
+ return new FakeCursor(table,
+ projection != null
+ ? projection
+ : new String[] { Telephony.CanonicalAddressesColumns.ADDRESS });
+ } else if (uri.toString().startsWith(
+ TelephonyBackupAgent.THREAD_ID_CONTENT_URI.toString())) {
+ List<String> recipients = uri.getQueryParameters("recipient");
+
+ final int threadId =
+ getOrCreateThreadId(recipients.toArray(new String[recipients.size()]));
+ List<ContentValues> table = new ArrayList<>();
+ ContentValues row = new ContentValues();
+ row.put(BaseColumns._ID, String.valueOf(threadId));
+ table.add(row);
+ return new FakeCursor(table, projection);
+ } else {
+ fail("Unknown URI");
+ }
+ return null;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ assertEquals(uri, Telephony.Threads.CONTENT_URI);
+ assertEquals(values.getAsInteger(Telephony.Threads.ARCHIVED).intValue(), 1);
+ final int threadId = Integer.parseInt(selectionArgs[0]);
+ mUpdateThreadsArchived.add(threadId);
+ return 1;
+ }
+ }
+
+ /**
+ * general cursor for serving queries.
+ */
+ private static class FakeCursor extends MockCursor {
+ String[] projection;
+ List<ContentValues> rows;
+ int nextRow = 0;
+
+ public FakeCursor(List<ContentValues> rows, String[] projection) {
+ this.projection = projection;
+ this.rows = rows;
+ }
+
+ public void setProjection(String[] projection) {
+ this.projection = projection;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return projection.length;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return projection[columnIndex];
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return rows.get(nextRow).getAsString(projection[columnIndex]);
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ return rows.get(nextRow).getAsInteger(projection[columnIndex]);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ return rows.get(nextRow).getAsLong(projection[columnIndex]);
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return nextRow >= getCount();
+ }
+
+ @Override
+ public boolean isLast() {
+ return nextRow == getCount() - 1;
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ nextRow = 0;
+ return getCount() > 0;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return getCount() > ++nextRow;
+ }
+
+ @Override
+ public int getCount() {
+ return rows.size();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ for (int i=0; i<projection.length; ++i) {
+ if (columnName.equals(projection[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public void close() {
+ }
+ }
+}