Merge "Remove MODULE_LICENSE_APACHE2 from TelephonyProvider" am: 905dff6098 am: 7fa89d81d8

Change-Id: I7ef2745dedaf26f8bfae9d4e8257032d9d294dcb
diff --git a/Android.bp b/Android.bp
index 1b6a559..b5498d5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,11 +1,11 @@
 android_app {
     name: "TelephonyProvider",
     privileged: true,
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "proto/**/*.proto"],
     platform_apis: true,
     certificate: "platform",
     libs: ["telephony-common"],
-    static_libs: ["android-common"],
+    static_libs: ["android-common", "telephonyprovider-protos"],
 }
 
 filegroup {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3a266e0..0a9802d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -26,6 +26,10 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
 
+    <!-- Used to access PlatformCompat for security fix enforcement -->
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+
     <protected-broadcast android:name="android.provider.action.EXTERNAL_PROVIDER_CHANGE" />
     <protected-broadcast android:name="android.intent.action.CONTENT_CHANGED" />
 
@@ -43,6 +47,7 @@
                  android:label="@string/app_label"
                  android:icon="@mipmap/ic_launcher_phone"
                  android:usesCleartextTraffic="true"
+                 android:forceQueryable="true"
                  android:defaultToDeviceProtectedStorage="true"
                  android:directBootAware="true">
 
diff --git a/proto/Android.bp b/proto/Android.bp
new file mode 100644
index 0000000..35dcc98
--- /dev/null
+++ b/proto/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 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.
+
+filegroup {
+    name: "telephonyprovider-proto-sources",
+    srcs: ["src/**/*.proto"],
+}
+
+java_library_static {
+    name: "telephonyprovider-protos",
+    proto: {
+        type: "nano",
+        output_params: [
+            "store_unknown_fields=true",
+            "enum_style=java",
+        ],
+    },
+    srcs: [":telephonyprovider-proto-sources"],
+    static_libs: ["libprotobuf-java-nano"],
+    sdk_version: "core_platform",
+    jarjar_rules: "jarjar-rules.txt",
+    // Pin java_version until jarjar is certified to support later versions. http://b/72703434
+    java_version: "1.8",
+}
diff --git a/proto/jarjar-rules.txt b/proto/jarjar-rules.txt
new file mode 100644
index 0000000..4cad1ee
--- /dev/null
+++ b/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.providers.telephony.protobuf.nano.@1
\ No newline at end of file
diff --git a/proto/src/carrierId.proto b/proto/src/carrierId.proto
new file mode 100644
index 0000000..9d2f29e
--- /dev/null
+++ b/proto/src/carrierId.proto
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2019 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.
+//
+
+syntax = "proto2";
+
+package carrierIdentification;
+
+option java_package = "com.android.providers.telephony";
+option java_outer_classname = "CarrierIdProto";
+
+// A complete list of carriers
+message CarrierList {
+  // A collection of carriers. one entry for one carrier.
+  repeated CarrierId carrier_id = 1;
+  // Version number of current carrier list
+  optional int32 version = 2;
+};
+
+// CarrierId is the unique representation of a carrier in CID table.
+message CarrierId {
+  // [Optional] A unique canonical number designated to a carrier.
+  optional int32 canonical_id = 1;
+
+  // [Optional] A user-friendly carrier name (not localized).
+  optional string carrier_name = 2;
+
+  // [Optional] Carrier attributes to match a carrier. At least one value is required.
+  repeated CarrierAttribute carrier_attribute = 3;
+
+  // [Optional] A unique canonical number to represent its parent carrier. The parent-child
+  // relationship can be used to differentiate a single carrier by different networks,
+  // by prepaid v.s. postpaid  or even by 4G v.s. 3G plan.
+  optional int32 parent_canonical_id = 4;
+};
+
+// Attributes used to match a carrier.
+// For each field within this message:
+//   - if not set, the attribute is ignored;
+//   - if set, the device must have one of the specified values to match.
+// Match is based on AND between any field that is set and OR for values within a repeated field.
+message CarrierAttribute {
+  // [Optional] The MCC and MNC that map to this carrier. At least one value is required.
+  repeated string mccmnc_tuple = 1;
+
+  // [Optional] Prefix of IMSI (International Mobile Subscriber Identity) in
+  // decimal format. Some digits can be replaced with "x" symbols matching any digit.
+  // Sample values: 20404794, 21670xx2xxx.
+  repeated string imsi_prefix_xpattern = 2;
+
+  // [Optional] The Service Provider Name. Read from subscription EF_SPN.
+  // Sample values: C Spire, LeclercMobile
+  repeated string spn = 3;
+
+  // [Optional] PLMN network name. Read from subscription EF_PNN.
+  // Sample values:
+  repeated string plmn = 4;
+
+  // [Optional] Group Identifier Level1 for a GSM phone. Read from subscription EF_GID1.
+  // Sample values: 6D, BAE0000000000000
+  repeated string gid1 = 5;
+
+  // [Optional] Group Identifier Level2 for a GSM phone. Read from subscription EF_GID2.
+  // Sample values: 6D, BAE0000000000000
+  repeated string gid2 = 6;
+
+  // [Optional] The Access Point Name, corresponding to "apn" field returned by
+  // "content://telephony/carriers/preferapn" on device.
+  // Sample values: fast.t-mobile.com, internet
+  repeated string preferred_apn = 7;
+
+  // [Optional] Prefix of Integrated Circuit Card Identifier. Read from subscription EF_ICCID.
+  // Sample values: 894430, 894410
+  repeated string iccid_prefix = 8;
+
+  // [Optional] Carrier Privilege Access Rule in hex string.
+  // Sample values: 61ed377e85d386a8dfee6b864bd85b0bfaa5af88
+  repeated string privilege_access_rule = 9;
+};
+
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 6cfcdfb..f3baf24 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/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">"Configuration du réseau mobile"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Stockage tél. et SMS/MMS"</string>
+    <string name="app_label" product="default" msgid="8338087656149558019">"Téléphone et stockage des messages"</string>
 </resources>
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
index 44f053d..a3c299f 100644
--- a/src/com/android/providers/telephony/CarrierIdProvider.java
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -25,23 +25,23 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
-import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.SystemProperties;
 import android.provider.Telephony.CarrierId;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.nano.CarrierIdProto;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.providers.telephony.nano.CarrierIdProto;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -54,8 +54,6 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import libcore.io.IoUtils;
-
 /**
  * This class provides the ability to query the Carrier Identification databases
  * (A.K.A. cid) which is stored in a SQLite database.
@@ -536,7 +534,7 @@
         } catch (IOException ex) {
             Log.e(TAG, "read carrier list from assets pb failure: " + ex);
         } finally {
-            IoUtils.closeQuietly(is);
+            FileUtils.closeQuietly(is);
         }
         try {
             is = new FileInputStream(new File(Environment.getDataDirectory(), OTA_UPDATED_PB_PATH));
@@ -544,7 +542,7 @@
         } catch (IOException ex) {
             Log.e(TAG, "read carrier list from ota pb failure: " + ex);
         } finally {
-            IoUtils.closeQuietly(is);
+            FileUtils.closeQuietly(is);
         }
 
         // compare version
@@ -553,7 +551,7 @@
             version = assets.version;
         }
         // bypass version check for ota carrier id test
-        if (ota != null && ((Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
+        if (ota != null && ((TelephonyUtils.IS_DEBUGGABLE && SystemProperties.getBoolean(
                 "persist.telephony.test.carrierid.ota", false))
                 || (ota.version > version))) {
             carrierList = ota;
@@ -607,13 +605,21 @@
 
         // Handle DEFAULT_SUBSCRIPTION_ID
         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            subId = SubscriptionController.getInstance().getDefaultSubId();
+            subId = SubscriptionManager.getDefaultSubscriptionId();
         }
 
-        if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
+        SubscriptionManager sm = (SubscriptionManager) getContext().getSystemService(
+            Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (!sm.isActiveSubscriptionId(subId)) {
             // Remove absent subId from the currentSubscriptionMap.
-            final List activeSubscriptions = Arrays.asList(SubscriptionController.getInstance()
-                    .getActiveSubIdList(false));
+            List activeSubscriptions = new ArrayList<>();
+            final List<SubscriptionInfo> subscriptionInfoList =
+                sm.getCompleteActiveSubscriptionInfoList();
+            if (subscriptionInfoList != null) {
+                for (SubscriptionInfo subInfo : subscriptionInfoList) {
+                    activeSubscriptions.add(subInfo.getSubscriptionId());
+                }
+            }
             int count = 0;
             for (int subscription : mCurrentSubscriptionMap.keySet()) {
                 if (!activeSubscriptions.contains(subscription)) {
@@ -633,7 +639,7 @@
 
     private Cursor queryCarrierIdForCurrentSubscription(Uri uri, String[] projectionIn) {
         // Parse the subId, using the default subId if subId is not provided
-        int subId = SubscriptionController.getInstance().getDefaultSubId();
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
         if (!TextUtils.isEmpty(uri.getLastPathSegment())) {
             try {
                 subId = Integer.parseInt(uri.getLastPathSegment());
@@ -645,7 +651,7 @@
 
         // Handle DEFAULT_SUBSCRIPTION_ID
         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            subId = SubscriptionController.getInstance().getDefaultSubId();
+            subId = SubscriptionManager.getDefaultSubscriptionId();
         }
 
         if (!mCurrentSubscriptionMap.containsKey(subId)) {
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 30158c3..96059d7 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -31,7 +31,6 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
@@ -44,6 +43,8 @@
 import android.provider.Telephony.Mms.Rate;
 import android.provider.Telephony.MmsSms;
 import android.provider.Telephony.Threads;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -518,9 +519,13 @@
                         // Give everyone rw permission until we encrypt the file
                         // (in PduPersister.persistData). Once the file is encrypted, the
                         // permissions will be set to 0644.
-                        int result = FileUtils.setPermissions(path, 0666, -1, -1);
-                        if (LOCAL_LOGV) {
-                            Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
+                        try {
+                            Os.chmod(path, 0666);
+                            if (LOCAL_LOGV) {
+                                Log.d(TAG, "MmsProvider.insert chmod is successful");
+                            }
+                        } catch (ErrnoException e) {
+                            Log.e(TAG, "Exception in chmod: " + e);
                         }
                     } catch (IOException e) {
                         Log.e(TAG, "createNewFile", e);
@@ -816,10 +821,13 @@
                 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
                         uri.getPathSegments().get(1);
                 // Reset the file permission back to read for everyone but me.
-                int result = FileUtils.setPermissions(path, 0644, -1, -1);
-                if (LOCAL_LOGV) {
-                    Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
-                            " for path: " + path);
+                try {
+                    Os.chmod(path, 0644);
+                    if (LOCAL_LOGV) {
+                        Log.d(TAG, "MmsProvider.update chmod is successful for path: " + path);
+                    }
+                } catch (ErrnoException e) {
+                    Log.e(TAG, "Exception in chmod: " + e);
                 }
                 return 0;
 
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index ff159f0..068f209 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -46,9 +46,9 @@
 import android.provider.Telephony.Threads;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
-import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.PhoneFactory;
 
 import com.google.android.mms.pdu.EncodedStringValue;
 import com.google.android.mms.pdu.PduHeaders;
@@ -266,7 +266,7 @@
 
     /**
      * The primary purpose of this DatabaseErrorHandler is to broadcast an intent on corruption and
-     * print a Slog.wtf so database corruption can be caught earlier.
+     * print a Log.wtf so database corruption can be caught earlier.
      */
     private static class MmsSmsDatabaseErrorHandler implements DatabaseErrorHandler {
         private DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler
@@ -294,6 +294,11 @@
         // Memory optimization - close idle connections after 30s of inactivity
         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
         setWriteAheadLoggingEnabled(false);
+        try {
+            PhoneFactory.addLocalLog(TAG, 100);
+        } catch (IllegalArgumentException e) {
+            // ignore
+        }
     }
 
     private static synchronized MmsSmsDatabaseErrorHandler getDbErrorHandler(Context context) {
@@ -552,10 +557,12 @@
 
     private static void localLog(String logMsg) {
         Log.d(TAG, logMsg);
+        PhoneFactory.localLog(TAG, logMsg);
     }
 
     private static void localLogWtf(String logMsg) {
-        Slog.wtf(TAG, logMsg);
+        Log.wtf(TAG, logMsg);
+        PhoneFactory.localLog(TAG, logMsg);
     }
 
     private boolean isInitialCreateDone() {
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index b59e14e..f5aeadc 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -42,7 +42,6 @@
 import android.provider.Telephony.ThreadsColumns;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Patterns;
 
 import com.google.android.mms.pdu.PduHeaders;
 
@@ -52,8 +51,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * This class provides the ability to query the MMS and SMS databases
@@ -105,17 +102,6 @@
     private static final int URI_MESSAGE_ID_TO_THREAD              = 18;
 
     /**
-     * Regex pattern for names and email addresses.
-     * <ul>
-     *     <li><em>mailbox</em> = {@code name-addr}</li>
-     *     <li><em>name-addr</em> = {@code [display-name] angle-addr}</li>
-     *     <li><em>angle-addr</em> = {@code [CFWS] "<" addr-spec ">" [CFWS]}</li>
-     * </ul>
-     */
-    public static final Pattern NAME_ADDR_EMAIL_PATTERN =
-            Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
-
-    /**
      * the name of the table that is used to store the queue of
      * messages(both MMS and SMS) to be sent/downloaded.
      */
@@ -554,8 +540,8 @@
      * Return the canonical address ID for this address.
      */
     private long getSingleAddressId(String address) {
-        boolean isEmail = isEmailAddress(address);
-        boolean isPhoneNumber = isPhoneNumber(address);
+        boolean isEmail = Mms.isEmailAddress(address);
+        boolean isPhoneNumber = Mms.isPhoneNumber(address);
 
         // We lowercase all email addresses, but not addresses that aren't numbers, because
         // that would incorrectly turn an address such as "My Vodafone" into "my vodafone"
@@ -1438,47 +1424,4 @@
         Log.w(LOG_TAG, "Ignored unsupported " + method + " call");
         return null;
     }
-
-    /**
-     * Is the specified address an email address?
-     *
-     * @param address the input address to test
-     * @return true if address is an email address; false otherwise.
-     */
-    private static boolean isEmailAddress(String address) {
-        if (TextUtils.isEmpty(address)) {
-            return false;
-        }
-
-        String s = extractAddrSpec(address);
-        Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
-        return match.matches();
-    }
-
-    /**
-     * Is the specified number a phone number?
-     *
-     * @param number the input number to test
-     * @return true if number is a phone number; false otherwise.
-     */
-    private static boolean isPhoneNumber(String number) {
-        if (TextUtils.isEmpty(number)) {
-            return false;
-        }
-
-        Matcher match = Patterns.PHONE.matcher(number);
-        return match.matches();
-    }
-
-    /**
-     * Helper method to extract email address from address string.
-     */
-    private static String extractAddrSpec(String address) {
-        Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
-
-        if (match.matches()) {
-            return match.group(2);
-        }
-        return address;
-    }
 }
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index b3c0d99..5c764c8 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.IntentService;
@@ -319,7 +320,7 @@
         final SubscriptionManager subscriptionManager = SubscriptionManager.from(this);
         if (subscriptionManager != null) {
             final List<SubscriptionInfo> subInfo =
-                    subscriptionManager.getActiveAndHiddenSubscriptionInfoList();
+                    subscriptionManager.getCompleteActiveSubscriptionInfoList();
             if (subInfo != null) {
                 for (SubscriptionInfo sub : subInfo) {
                     final String phoneNumber = getNormalizedNumber(sub);
@@ -443,48 +444,54 @@
             return;
         }
 
-        int messagesWritten = 0;
+        // Backups consist of multiple chunks; each chunk consists of a set of messages
+        // of the same type in a chronological order.
+        BackupChunkInformation chunk;
         try (JsonWriter jsonWriter = getJsonWriter(fileName)) {
             if (fileName.endsWith(SMS_BACKUP_FILE_SUFFIX)) {
-                messagesWritten = putSmsMessagesToJson(cursor, jsonWriter);
+                chunk = putSmsMessagesToJson(cursor, jsonWriter);
             } else {
-                messagesWritten = putMmsMessagesToJson(cursor, jsonWriter);
+                chunk = putMmsMessagesToJson(cursor, jsonWriter);
             }
         }
-        backupFile(messagesWritten, fileName, data);
+        backupFile(chunk, fileName, data);
     }
 
     @VisibleForTesting
-    int putMmsMessagesToJson(Cursor cursor,
+    @NonNull
+    BackupChunkInformation putMmsMessagesToJson(Cursor cursor,
                              JsonWriter jsonWriter) throws IOException {
+        BackupChunkInformation results = new BackupChunkInformation();
         jsonWriter.beginArray();
-        int msgCount;
-        for (msgCount = 0; msgCount < mMaxMsgPerFile && !cursor.isAfterLast();
+        for (; results.count < mMaxMsgPerFile && !cursor.isAfterLast();
                 cursor.moveToNext()) {
-            msgCount += writeMmsToWriter(jsonWriter, cursor);
+            writeMmsToWriter(jsonWriter, cursor, results);
         }
         jsonWriter.endArray();
-        return msgCount;
+        return results;
     }
 
     @VisibleForTesting
-    int putSmsMessagesToJson(Cursor cursor, JsonWriter jsonWriter) throws IOException {
-
+    @NonNull
+    BackupChunkInformation putSmsMessagesToJson(Cursor cursor, JsonWriter jsonWriter)
+      throws IOException {
+        BackupChunkInformation results = new BackupChunkInformation();
         jsonWriter.beginArray();
-        int msgCount;
-        for (msgCount = 0; msgCount < mMaxMsgPerFile && !cursor.isAfterLast();
-                ++msgCount, cursor.moveToNext()) {
-            writeSmsToWriter(jsonWriter, cursor);
+        for (; results.count < mMaxMsgPerFile && !cursor.isAfterLast();
+                ++results.count, cursor.moveToNext()) {
+            writeSmsToWriter(jsonWriter, cursor, results);
         }
         jsonWriter.endArray();
-        return msgCount;
+        return results;
     }
 
-    private void backupFile(int messagesWritten, String fileName, FullBackupDataOutput data)
+    private void backupFile(BackupChunkInformation chunkInformation, String fileName,
+        FullBackupDataOutput data)
             throws IOException {
         final File file = new File(getFilesDir().getPath() + "/" + fileName);
+        file.setLastModified(chunkInformation.timestamp);
         try {
-            if (messagesWritten > 0) {
+            if (chunkInformation.count > 0) {
                 if (mBytesOverQuota > 0) {
                     mBytesOverQuota -= file.length();
                     return;
@@ -531,9 +538,7 @@
 
                 for (File file : files) {
                     final String fileName = file.getName();
-                    if (DEBUG) {
-                        Log.d(TAG, "onHandleIntent restoring file " + fileName);
-                    }
+                    Log.d(TAG, "onHandleIntent restoring file " + fileName);
                     try (FileInputStream fileInputStream = new FileInputStream(file)) {
                         mTelephonyBackupAgent.doRestoreFile(fileName, fileInputStream.getFD());
                         didRestore = true;
@@ -547,9 +552,7 @@
                 if (didRestore) {
                   // Tell the default sms app to do a full sync now that the messages have been
                   // restored.
-                  if (DEBUG) {
-                    Log.d(TAG, "onHandleIntent done - notifying default sms app");
-                  }
+                  Log.d(TAG, "onHandleIntent done - notifying default sms app");
                   ProviderUtil.notifyIfNotDefaultSmsApp(null /*uri*/, null /*calling package*/,
                       this);
                 }
@@ -605,25 +608,17 @@
     }
 
     private void doRestoreFile(String fileName, FileDescriptor fd) throws IOException {
-        if (DEBUG) {
-            Log.d(TAG, "Restoring file " + fileName);
-        }
+        Log.d(TAG, "Restoring file " + fileName);
 
         try (JsonReader jsonReader = getJsonReader(fd)) {
             if (fileName.endsWith(SMS_BACKUP_FILE_SUFFIX)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Restoring SMS");
-                }
+                Log.d(TAG, "Restoring SMS");
                 putSmsMessagesToProvider(jsonReader);
             } else if (fileName.endsWith(MMS_BACKUP_FILE_SUFFIX)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Restoring text MMS");
-                }
+                Log.d(TAG, "Restoring text MMS");
                 putMmsMessagesToProvider(jsonReader);
             } else {
-                if (DEBUG) {
-                    Log.e(TAG, "Unknown file to restore:" + fileName);
-                }
+                Log.e(TAG, "Unknown file to restore:" + fileName);
             }
         }
     }
@@ -654,6 +649,7 @@
     @VisibleForTesting
     void putMmsMessagesToProvider(JsonReader jsonReader) throws IOException {
         jsonReader.beginArray();
+        int total = 0;
         while (jsonReader.hasNext()) {
             final Mms mms = readMmsFromReader(jsonReader);
             if (DEBUG) {
@@ -662,11 +658,15 @@
             if (doesMmsExist(mms)) {
                 if (DEBUG) {
                     Log.e(TAG, String.format("Mms: %s already exists", mms.toString()));
+                } else {
+                    Log.w(TAG, "Mms: Found duplicate MMS");
                 }
                 continue;
             }
+            total++;
             addMmsMessage(mms);
         }
+        Log.d(TAG, "putMmsMessagesToProvider handled " + total + " new messages.");
     }
 
     @VisibleForTesting
@@ -706,11 +706,19 @@
         if (subscriptionInfo == null) {
             return null;
         }
-        return PhoneNumberUtils.formatNumberToE164(subscriptionInfo.getNumber(),
-                subscriptionInfo.getCountryIso().toUpperCase(Locale.US));
+        // country iso might not be always available in some corner cases (e.g. mis-configured SIM,
+        // carrier config, or test SIM has incorrect IMSI, etc...). In that case, just return the
+        // unformatted number.
+        if (!TextUtils.isEmpty(subscriptionInfo.getCountryIso())) {
+            return PhoneNumberUtils.formatNumberToE164(subscriptionInfo.getNumber(),
+                    subscriptionInfo.getCountryIso().toUpperCase(Locale.US));
+        } else {
+            return subscriptionInfo.getNumber();
+        }
     }
 
-    private void writeSmsToWriter(JsonWriter jsonWriter, Cursor cursor) throws IOException {
+    private void writeSmsToWriter(JsonWriter jsonWriter, Cursor cursor,
+            BackupChunkInformation chunk) throws IOException {
         jsonWriter.beginObject();
 
         for (int i=0; i<cursor.getColumnCount(); ++i) {
@@ -733,12 +741,31 @@
                     break;
                 case Telephony.Sms._ID:
                     break;
+                case Telephony.Sms.DATE:
+                case Telephony.Sms.DATE_SENT:
+                    chunk.timestamp = findNewestValue(chunk.timestamp, value);
+                    jsonWriter.name(name).value(value);
+                    break;
                 default:
                     jsonWriter.name(name).value(value);
                     break;
             }
         }
         jsonWriter.endObject();
+    }
+
+    private long findNewestValue(long current, String latest) {
+        if(latest == null) {
+            return current;
+        }
+
+        try {
+            long latestLong = Long.valueOf(latest);
+            return Math.max(current, latestLong);
+        } catch (NumberFormatException e) {
+            Log.d(TAG, "Unable to parse value "+latest);
+            return current;
+        }
 
     }
 
@@ -823,6 +850,8 @@
                 default:
                     if (DEBUG) {
                         Log.w(TAG, "readSmsValuesFromReader Unknown name:" + name);
+                    } else {
+                        Log.w(TAG, "readSmsValuesFromReader encountered unknown name.");
                     }
                     jsonReader.skipValue();
                     break;
@@ -843,12 +872,13 @@
         return recipients;
     }
 
-    private int writeMmsToWriter(JsonWriter jsonWriter, Cursor cursor) throws IOException {
+    private void writeMmsToWriter(JsonWriter jsonWriter, Cursor cursor,
+            BackupChunkInformation chunk) throws IOException {
         final int mmsId = cursor.getInt(ID_IDX);
         final MmsBody body = getMmsBody(mmsId);
         // We backup any message that contains text, but only backup the text part.
         if (body == null || body.text == null) {
-            return 0;
+            return;
         }
 
         boolean subjectNull = true;
@@ -877,6 +907,11 @@
                 case Telephony.Mms._ID:
                 case Telephony.Mms.SUBJECT_CHARSET:
                     break;
+                case Telephony.Mms.DATE:
+                case Telephony.Mms.DATE_SENT:
+                    chunk.timestamp = findNewestValue(chunk.timestamp, value);
+                    jsonWriter.name(name).value(value);
+                    break;
                 case Telephony.Mms.SUBJECT:
                     subjectNull = false;
                 default:
@@ -896,7 +931,7 @@
             writeStringToWriter(jsonWriter, cursor, Telephony.Mms.SUBJECT_CHARSET);
         }
         jsonWriter.endObject();
-        return 1;
+        chunk.count++;
     }
 
     private Mms readMmsFromReader(JsonReader jsonReader) throws IOException {
@@ -955,9 +990,7 @@
                     mms.values.put(name, jsonReader.nextString());
                     break;
                 default:
-                    if (DEBUG) {
-                        Log.d(TAG, "Unknown name:" + name);
-                    }
+                    Log.d(TAG, "Unknown JSON element name:" + name);
                     jsonReader.skipValue();
                     break;
             }
@@ -995,9 +1028,7 @@
                 values,
                 ARCHIVE_THREAD_SELECTION,
                 new String[] { Long.toString(threadId)}) != 1) {
-            if (DEBUG) {
-                Log.e(TAG, "archiveThread: failed to update database");
-            }
+            Log.e(TAG, "archiveThread: failed to update database");
         }
     }
 
@@ -1066,9 +1097,7 @@
                         addrValues.put(name, jsonReader.nextString());
                         break;
                     default:
-                        if (DEBUG) {
-                            Log.d(TAG, "Unknown name:" + name);
-                        }
+                        Log.d(TAG, "Unknown JSON Element name:" + name);
                         jsonReader.skipValue();
                         break;
                 }
@@ -1099,9 +1128,7 @@
                         attachmentValues.put(name, jsonReader.nextString());
                         break;
                     default:
-                        if (DEBUG) {
-                            Log.d(TAG, "getMmsAttachmentsFromReader Unknown name:" + name);
-                        }
+                        Log.d(TAG, "getMmsAttachmentsFromReader Unknown name:" + name);
                         jsonReader.skipValue();
                         break;
                 }
@@ -1110,9 +1137,7 @@
             if (attachmentValues.containsKey(MMS_ATTACHMENT_FILENAME)) {
                 mms.attachments.add(attachmentValues);
             } else {
-                if (DEBUG) {
-                    Log.d(TAG, "Attachment json with no filenames");
-                }
+                Log.d(TAG, "Attachment json with no filenames");
             }
         }
         jsonReader.endArray();
@@ -1140,9 +1165,7 @@
             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");
-                }
+                Log.e(TAG, "Could not insert SMIL part");
                 return;
             }
         }
@@ -1162,9 +1185,7 @@
             values.put(Telephony.Mms.Part.TEXT, mms.body == null ? "" : mms.body.text);
 
             if (mContentResolver.insert(partUri, values) == null) {
-                if (DEBUG) {
-                    Log.e(TAG, "Could not insert body part");
-                }
+                Log.e(TAG, "Could not insert body part");
                 return;
             }
         }
@@ -1184,9 +1205,7 @@
                         getDataDir() + ATTACHMENT_DATA_PATH + filename);
                 Uri newPartUri = mContentResolver.insert(partUri, values);
                 if (newPartUri == null) {
-                    if (DEBUG) {
-                        Log.e(TAG, "Could not insert attachment part");
-                    }
+                    Log.e(TAG, "Could not insert attachment part");
                     return;
                 }
             }
@@ -1195,9 +1214,7 @@
         // 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");
-            }
+            Log.e(TAG, "Could not insert mms");
             return;
         }
 
@@ -1301,9 +1318,7 @@
             try {
                 threadId = Telephony.Threads.getOrCreateThreadId(this, recipients);
             } catch (RuntimeException e) {
-                if (DEBUG) {
-                    Log.e(TAG, e.toString());
-                }
+                Log.e(TAG, "Problem obtaining thread.", e);
             }
             mCacheGetOrCreateThreadId.put(recipients, threadId);
             return threadId;
@@ -1377,15 +1392,11 @@
             try {
                 longId = Long.parseLong(id);
                 if (longId < 0) {
-                    if (DEBUG) {
-                        Log.e(TAG, "getAddresses: invalid id " + longId);
-                    }
+                    Log.e(TAG, "getAddresses: invalid id " + longId);
                     continue;
                 }
             } catch (final NumberFormatException ex) {
-                if (DEBUG) {
-                    Log.e(TAG, "getAddresses: invalid id. " + ex, ex);
-                }
+                Log.e(TAG, "getAddresses: invalid id " + ex, ex);
                 // skip this id
                 continue;
             }
@@ -1397,10 +1408,9 @@
                         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);
-                }
+                Log.e(TAG, "getAddresses: query failed for id " + longId, e);
             }
+
             if (c != null) {
                 try {
                     if (c.moveToFirst()) {
@@ -1408,9 +1418,7 @@
                         if (!TextUtils.isEmpty(number)) {
                             numbers.add(number);
                         } else {
-                            if (DEBUG) {
-                                Log.d(TAG, "Canonical MMS/SMS address is empty for id: " + longId);
-                            }
+                            Log.d(TAG, "Canonical MMS/SMS address is empty for id: " + longId);
                         }
                     }
                 } finally {
@@ -1419,9 +1427,7 @@
             }
         }
         if (numbers.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]");
-            }
+            Log.d(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]");
         }
         return numbers;
     }
@@ -1441,4 +1447,12 @@
     public static boolean getIsRestoring() {
         return sIsRestoring;
     }
+
+    private static class BackupChunkInformation {
+        // Timestamp of the recent message in the file
+        private long timestamp;
+
+        // The number of messages in the backup file
+        private int count = 0;
+    }
 }
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 3e1ffcf..7cc518a 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -71,6 +71,7 @@
 import static android.provider.Telephony.Carriers._ID;
 
 import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -94,15 +95,14 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Telephony;
 import android.telephony.Annotation;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -114,6 +114,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.util.XmlUtils;
 import android.service.carrier.IApnSourceService;
 
@@ -122,18 +123,21 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.zip.CheckedInputStream;
 import java.util.zip.CRC32;
 
 public class TelephonyProvider extends ContentProvider
@@ -645,10 +649,18 @@
         }
 
         private long getChecksum(File file) {
-            long checksum = -1;
-            try {
-                checksum = FileUtils.checksumCrc32(file);
-                if (DBG) log("Checksum for " + file.getAbsolutePath() + " is " + checksum);
+            CRC32 checkSummer = new CRC32();
+            long checkSum = -1;
+            try (CheckedInputStream cis =
+                new CheckedInputStream(new FileInputStream(file), checkSummer)){
+                byte[] buf = new byte[128];
+                if(cis != null) {
+                    while(cis.read(buf) >= 0) {
+                        // Just read for checksum to get calculated.
+                    }
+                }
+                checkSum = checkSummer.getValue();
+                if (DBG) log("Checksum for " + file.getAbsolutePath() + " is " + checkSum);
             } catch (FileNotFoundException e) {
                 loge("FileNotFoundException for " + file.getAbsolutePath() + ":" + e);
             } catch (IOException e) {
@@ -660,14 +672,14 @@
             try (InputStream inputStream = mContext.getResources().
                         openRawResource(com.android.internal.R.xml.apns)) {
                 byte[] array = toByteArray(inputStream);
-                CRC32 c = new CRC32();
-                c.update(array);
-                checksum += c.getValue();
-                if (DBG) log("Checksum after adding resource is " + checksum);
+                checkSummer.reset();
+                checkSummer.update(array);
+                checkSum += checkSummer.getValue();
+                if (DBG) log("Checksum after adding resource is " + checkSummer.getValue());
             } catch (IOException | Resources.NotFoundException e) {
                 loge("Exception when calculating checksum for internal apn resources: " + e);
             }
-            return checksum;
+            return checkSum;
         }
 
         private byte[] toByteArray(InputStream input) throws IOException {
@@ -2565,6 +2577,7 @@
     private void restoreApnsWithService(int subId) {
         Context context = getContext();
         Resources r = context.getResources();
+        AtomicBoolean connectionBindingInvalid = new AtomicBoolean(false);
         ServiceConnection connection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName className,
@@ -2583,6 +2596,24 @@
                     mIApnSourceService = null;
                 }
             }
+
+            @Override
+            public void onBindingDied(ComponentName name) {
+                loge("The binding to the apn service connection is dead: " + name);
+                synchronized (mLock) {
+                    connectionBindingInvalid.set(true);
+                    mLock.notifyAll();
+                }
+            }
+
+            @Override
+            public void onNullBinding(ComponentName name) {
+                loge("Null binding: " + name);
+                synchronized (mLock) {
+                    connectionBindingInvalid.set(true);
+                    mLock.notifyAll();
+                }
+            }
         };
 
         Intent intent = new Intent(IApnSourceService.class.getName());
@@ -2593,13 +2624,17 @@
             if (context.bindService(intent, connection, Context.BIND_IMPORTANT |
                         Context.BIND_AUTO_CREATE)) {
                 synchronized (mLock) {
-                    while (mIApnSourceService == null) {
+                    while (mIApnSourceService == null && !connectionBindingInvalid.get()) {
                         try {
                             mLock.wait();
                         } catch (InterruptedException e) {
                             loge("Error while waiting for service connection: " + e);
                         }
                     }
+                    if (connectionBindingInvalid.get()) {
+                        loge("The binding is invalid.");
+                        return;
+                    }
                     try {
                         ContentValues[] values = mIApnSourceService.getApns(subId);
                         if (values != null) {
@@ -2632,6 +2667,12 @@
     public boolean onCreate() {
         mOpenHelper = new DatabaseHelper(getContext());
 
+        try {
+            PhoneFactory.addLocalLog(TAG, 100);
+        } catch (IllegalArgumentException e) {
+            // ignore
+        }
+
         boolean isNewBuild = false;
         String newBuildId = SystemProperties.get("ro.build.id", null);
         if (!TextUtils.isEmpty(newBuildId)) {
@@ -2698,6 +2739,7 @@
 
     private static void localLog(String logMsg) {
         Log.d(TAG, logMsg);
+        PhoneFactory.localLog(TAG, logMsg);
     }
 
     private synchronized boolean isManagedApnEnforced() {
@@ -2882,7 +2924,7 @@
         List<String> constraints = new ArrayList<String>();
 
         int match = s_urlMatcher.match(url);
-        checkPermission();
+        checkPermissionCompat(match, projectionIn);
         switch (match) {
             case URL_TELEPHONY_USING_SUBID: {
                 subIdString = url.getLastPathSegment();
@@ -3149,7 +3191,7 @@
             }
 
             boolean isMVNOAPN = !TextUtils.isEmpty(ret.getString(numericIndex))
-                    && tm.isCurrentSimOperator(ret.getString(numericIndex),
+                    && tm.matchesCurrentSimOperator(ret.getString(numericIndex),
                             getMvnoTypeIntFromString(ret.getString(mvnoIndex)),
                             ret.getString(mvnoDataIndex));
             boolean isMNOAPN = !TextUtils.isEmpty(ret.getString(numericIndex))
@@ -3900,16 +3942,51 @@
                 return;
             }
         }
-        throw new SecurityException("No permission to write APN settings");
+
+
+        throw new SecurityException("No permission to access APN settings");
     }
 
-    private void checkPhonePrivilegePermission() {
-        int status = getContext().checkCallingOrSelfPermission(
-                "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        if (status == PackageManager.PERMISSION_GRANTED) {
-            return;
+    /**
+     * Check permission to query the database based on PlatformCompat settings -- if the compat
+     * change is enabled, check WRITE_APN_SETTINGS or carrier privs for all queries. Otherwise,
+     * use the legacy checkQueryPermission method to see if the query should be allowed.
+     */
+    private void checkPermissionCompat(int match, String[] projectionIn) {
+        boolean useNewBehavior = CompatChanges.isChangeEnabled(
+                Telephony.Carriers.APN_READING_PERMISSION_CHANGE_ID,
+                Binder.getCallingUid());
+
+        if (!useNewBehavior) {
+            log("Using old permission behavior for telephony provider compat");
+            checkQueryPermission(match, projectionIn);
+        } else {
+            checkPermission();
         }
-        throw new SecurityException("No phone privilege permission");
+    }
+
+    private void checkQueryPermission(int match, String[] projectionIn) {
+        if (match != URL_SIMINFO) {
+            if (projectionIn != null) {
+                for (String column : projectionIn) {
+                    if (TYPE.equals(column) ||
+                            MMSC.equals(column) ||
+                            MMSPROXY.equals(column) ||
+                            MMSPORT.equals(column) ||
+                            MVNO_TYPE.equals(column) ||
+                            MVNO_MATCH_DATA.equals(column) ||
+                            APN.equals(column)) {
+                        // noop
+                    } else {
+                        checkPermission();
+                        break;
+                    }
+                }
+            } else {
+                // null returns all columns, so need permission check
+                checkPermission();
+            }
+        }
     }
 
     private DatabaseHelper mOpenHelper;
@@ -3968,7 +4045,7 @@
                 String mvnoType = cursor.getString(0 /* MVNO_TYPE index */);
                 String mvnoMatchData = cursor.getString(1 /* MVNO_MATCH_DATA index */);
                 if (!TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)
-                        && telephonyManager.isCurrentSimOperator(simOperator,
+                        && telephonyManager.matchesCurrentSimOperator(simOperator,
                             getMvnoTypeIntFromString(mvnoType), mvnoMatchData)) {
                     where = NUMERIC + "='" + simOperator + "'"
                             + " AND " + MVNO_TYPE + "='" + mvnoType + "'"
diff --git a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
index bc39260..ee0e016 100644
--- a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
@@ -17,6 +17,7 @@
 package com.android.providers.telephony;
 
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
@@ -26,23 +27,21 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.Telephony.CarrierId;
+import android.telephony.SubscriptionManager;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.util.Log;
 
-import com.android.internal.telephony.SubscriptionController;
-
 import junit.framework.TestCase;
 
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.lang.reflect.Field;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * Tests for testing CRUD operations of CarrierIdProvider.
@@ -77,12 +76,12 @@
     private CarrierIdProviderTestable mCarrierIdProviderTestable;
     private FakeContentObserver mContentObserver;
     private SharedPreferences mSharedPreferences = mock(SharedPreferences.class);
-    private SubscriptionController mSubController = mock(SubscriptionController.class);
+    private SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
 
     private class FakeContentResolver extends MockContentResolver {
         @Override
-        public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
-            super.notifyChange(uri, observer, syncToNetwork);
+        public void notifyChange(Uri uri, ContentObserver observer) {
+            super.notifyChange(uri, observer);
             Log.d(TAG, "onChanged(uri=" + uri + ")" + observer);
             mContentObserver.dispatchChange(false, uri);
         }
@@ -121,8 +120,13 @@
 
         @Override
         public Object getSystemService(String name) {
-            Log.d(TAG, "getSystemService: returning null");
-            return null;
+            switch (name) {
+                case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+                    return subscriptionManager;
+                default:
+                    Log.d(TAG, "getSystemService: returning null");
+                    return null;
+            }
         }
 
         @Override
@@ -148,12 +152,6 @@
         mContext = new MockContextWithProvider(mCarrierIdProviderTestable);
         mContentResolver = mContext.getContentResolver();
         mContentObserver = new FakeContentObserver(null);
-
-        doReturn("").when(mSubController).getDataEnabledOverrideRules(anyInt());
-
-        Field field = SubscriptionController.class.getDeclaredField("sInstance");
-        field.setAccessible(true);
-        field.set(null, mSubController);
     }
 
     @Override
@@ -341,8 +339,7 @@
             ContentValues cv = new ContentValues();
             cv.put(CarrierId.CARRIER_ID, dummy_cid);
             cv.put(CarrierId.CARRIER_NAME, dummy_name);
-            doReturn(1).when(mSubController).getDefaultSubId();
-            doReturn(true).when(mSubController).isActiveSubId(eq(1));
+            when(subscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(true);
             mContext.getContentResolver().update(Uri.withAppendedPath(CarrierId.CONTENT_URI,
                     "1"), cv, null, null);
         } catch (Exception e) {
@@ -351,7 +348,6 @@
         }
         int carrierId = -1;
         String carrierName = null;
-
         // query carrier id for subId 1
         try {
             final Cursor c = mContext.getContentResolver().query(
@@ -416,9 +412,6 @@
             ContentValues cv = new ContentValues();
             cv.put(CarrierId.CARRIER_ID, dummy_cid);
             cv.put(CarrierId.CARRIER_NAME, dummy_name);
-            doReturn(1).when(mSubController).getDefaultSubId();
-            doReturn(true).when(mSubController).isActiveSubId(eq(1));
-
             mContext.getContentResolver().update(CarrierId.CONTENT_URI, cv, null, null);
             Assert.fail("should throw an exception for wrong uri");
         } catch (IllegalArgumentException ex) {
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index 37cf6e3..b1cd5e4 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -16,7 +16,10 @@
 
 package com.android.providers.telephony;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import android.annotation.TargetApi;
+import android.app.backup.BackupDataOutput;
 import android.app.backup.FullBackupDataOutput;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -26,6 +29,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
+import android.os.ParcelFileDescriptor;
 import android.provider.BaseColumns;
 import android.provider.Telephony;
 import android.test.AndroidTestCase;
@@ -40,22 +44,29 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import libcore.io.IoUtils;
+
 import com.google.android.mms.pdu.CharacterSets;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for testing backup/restore of SMS and text MMS messages.
@@ -684,6 +695,44 @@
         assertEquals(backupSizeAfterSecondQuotaHit, fullBackupDataOutput.getSize());
     }
 
+    /**
+     * Test backups are consistent between runs. This ensures that when no data
+     * has changed between backup runs we don't generate a diff which needs to
+     * be sent to the server.
+     * @throws Exception
+     */
+    public void testBackup_WithoutChanges_DoesNotChangeOutput() throws Exception {
+        mSmsTable.addAll(Arrays.asList(mSmsRows));
+        mMmsTable.addAll(Arrays.asList(mMmsRows));
+
+        byte[] firstBackup = getBackup("1");
+        // Ensure there is some time between backup runs. This is the way to identify
+        // time dependent backup contents.
+        Thread.sleep(TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS));
+        byte[] secondBackup = getBackup("2");
+
+        // Make sure something has been backed up.
+        assertFalse(firstBackup == null || firstBackup.length == 0);
+
+        // Make sure the two backups are the same.
+        assertArrayEquals(firstBackup, secondBackup);
+    }
+
+    private byte[] getBackup(String runId) throws IOException {
+        File cacheDir = getContext().getCacheDir();
+        File backupOutput = File.createTempFile("backup", runId, cacheDir);
+        ParcelFileDescriptor outputFd =
+                ParcelFileDescriptor.open(backupOutput, ParcelFileDescriptor.MODE_WRITE_ONLY);
+        try {
+            FullBackupDataOutput fullBackupDataOutput = new FullBackupDataOutput(outputFd);
+            mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
+            return IoUtils.readFileAsByteArray(backupOutput.getAbsolutePath());
+        } finally {
+            outputFd.close();
+            backupOutput.delete();
+        }
+    }
+
     // 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);
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index 2018e2c..18c8d08 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -1363,9 +1363,9 @@
         otherValues.put(Carriers.MVNO_TYPE, otherMvnoTyp);
         otherValues.put(Carriers.MVNO_MATCH_DATA, otherMvnoMatchData);
 
-        doReturn(true).when(telephonyManager).isCurrentSimOperator(
+        doReturn(true).when(telephonyManager).matchesCurrentSimOperator(
             anyString(), anyInt(), eq(TelephonyProviderTestable.TEST_SPN));
-        doReturn(false).when(telephonyManager).isCurrentSimOperator(
+        doReturn(false).when(telephonyManager).matchesCurrentSimOperator(
             anyString(), anyInt(), eq(otherMvnoMatchData));
 
         // insert APNs
@@ -1506,9 +1506,9 @@
 
         TelephonyManager telephonyManager =
             (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        doReturn(true).when(telephonyManager).isCurrentSimOperator(
+        doReturn(true).when(telephonyManager).matchesCurrentSimOperator(
             anyString(), anyInt(), eq(mvnoData));
-        doReturn(false).when(telephonyManager).isCurrentSimOperator(
+        doReturn(false).when(telephonyManager).matchesCurrentSimOperator(
             anyString(), anyInt(), eq(""));
 
         // Query DB