merge in nyc-release history after reset to nyc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 88acca2..36d5ef6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -37,8 +37,8 @@
                  android:label="@string/app_label"
                  android:icon="@mipmap/ic_launcher_phone"
                  android:usesCleartextTraffic="true"
-                 android:forceDeviceEncrypted="true"
-                 android:encryptionAware="true">
+                 android:defaultToDeviceProtectedStorage="true"
+                 android:directBootAware="true">
 
         <provider android:name="TelephonyProvider"
                   android:authorities="telephony"
@@ -88,5 +88,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-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-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/ProviderUtil.java b/src/com/android/providers/telephony/ProviderUtil.java
index 7053e9a..4234b06 100644
--- a/src/com/android/providers/telephony/ProviderUtil.java
+++ b/src/com/android/providers/telephony/ProviderUtil.java
@@ -113,16 +113,16 @@
     }
 
     public static Context getCredentialEncryptedContext(Context context) {
-        if (context.isCredentialEncryptedStorage()) {
+        if (context.isCredentialProtectedStorage()) {
             return context;
         }
-        return context.createCredentialEncryptedStorageContext();
+        return context.createCredentialProtectedStorageContext();
     }
 
     public static Context getDeviceEncryptedContext(Context context) {
-        if (context.isDeviceEncryptedStorage()) {
+        if (context.isDeviceProtectedStorage()) {
             return context;
         }
-        return context.createDeviceEncryptedStorageContext();
+        return context.createDeviceProtectedStorageContext();
     }
 }
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index e860332..5956d33 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -22,6 +22,8 @@
 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;
@@ -29,6 +31,9 @@
 import android.content.ContentProvider;
 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;
@@ -47,18 +52,23 @@
 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.io.UnsupportedEncodingException;
 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;
 
@@ -132,9 +142,13 @@
     // JSON key for MMS charset.
     private static final String MMS_BODY_CHARSET_KEY = "mms_charset";
 
-    // File names for backup/restore.
-    private static final String SMS_BACKUP_FILE = "sms_backup";
-    private static final String MMS_BACKUP_FILE = "mms_backup";
+    // 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";
@@ -142,8 +156,7 @@
     // Order by ID entries from database.
     private static final String ORDER_BY_ID = BaseColumns._ID + " ASC";
 
-    // Order by Date entries from database. We order it the oldest first in order to throw them if
-    // we are over quota.
+    // Order by Date entries from database. We start backup from the oldest.
     private static final String ORDER_BY_DATE = "date ASC";
 
     // Columns from SMS database for backup/restore.
@@ -178,7 +191,6 @@
             Telephony.Mms.DATE_SENT,
             Telephony.Mms.MESSAGE_TYPE,
             Telephony.Mms.MMS_VERSION,
-            Telephony.Mms.TEXT_ONLY,
             Telephony.Mms.MESSAGE_BOX,
             Telephony.Mms.CONTENT_LOCATION,
             Telephony.Mms.THREAD_ID
@@ -197,60 +209,82 @@
     // and charset for MMS message.
     @VisibleForTesting
     static final String[] MMS_TEXT_PROJECTION = new String[] {
-            Telephony.Mms.Part.CONTENT_TYPE,
             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.
-    private static final int MAX_MSG_PER_FILE = 1000;
+    @VisibleForTesting
+    static int MAX_MSG_PER_FILE = 1000;
 
 
     // Default values for SMS, MMS, Addresses restore.
-    private static final ContentValues defaultValuesSms = new ContentValues(3);
-    private static final ContentValues defaultValuesMms = new ContentValues(5);
-    private static final ContentValues defaultValuesAddr = new ContentValues(2);
+    private static final ContentValues sDefaultValuesSms = new ContentValues(3);
+    private static final ContentValues sDefaultValuesMms = new ContentValues(5);
+    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 bytes over.
-    private static final String BYTES_OVER_QUOTA_PREF_KEY = "bytes_over_quota";
+    // 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.
-        defaultValuesSms.put(Telephony.Sms.READ, 1);
-        defaultValuesSms.put(Telephony.Sms.SEEN, 1);
+        sDefaultValuesSms.put(Telephony.Sms.READ, 1);
+        sDefaultValuesSms.put(Telephony.Sms.SEEN, 1);
         // If there is no sub_id with self phone number on restore set it to -1.
-        defaultValuesSms.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
+        sDefaultValuesSms.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
 
-        defaultValuesMms.put(Telephony.Mms.READ, 1);
-        defaultValuesMms.put(Telephony.Mms.SEEN, 1);
-        defaultValuesMms.put(Telephony.Mms.SUBSCRIPTION_ID, -1);
-        defaultValuesMms.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_ALL);
-        defaultValuesMms.put(Telephony.Mms.TEXT_ONLY, 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);
 
-        defaultValuesAddr.put(Telephony.Mms.Addr.TYPE, 0);
-        defaultValuesAddr.put(Telephony.Mms.Addr.CHARSET, CharacterSets.DEFAULT_CHARSET);
+        sDefaultValuesAddr.put(Telephony.Mms.Addr.TYPE, 0);
+        sDefaultValuesAddr.put(Telephony.Mms.Addr.CHARSET, CharacterSets.DEFAULT_CHARSET);
     }
 
 
-    private SparseArray<String> subId2phone;
-    private Map<String, Integer> phone2subId;
+    private final SparseArray<String> subId2phone = new SparseArray<String>();
+
+    // It's static cause it's used by DeferredSmsMmsRestoreService.
+    private static final Map<String, Integer> sPhone2subId = new ArrayMap<String, Integer>();
     private ContentProvider mSmsProvider;
     private ContentProvider mMmsProvider;
     private ContentProvider mMmsSmsProvider;
 
-    // How many bytes we have to skip to fit into quota.
+    // 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
+    static Map<Long, List<String>> sCacheRecipientsByThread;
+    // Cache threadId by list of recipients. Used during restore.
+    @VisibleForTesting
+    static Map<Set<String>, Long> sCacheGetOrCreateThreadId;
+
     @Override
     public void onCreate() {
         super.onCreate();
 
-        subId2phone = new SparseArray<String>();
-        phone2subId = new ArrayMap<String, Integer>();
         final SubscriptionManager subscriptionManager = SubscriptionManager.from(this);
         if (subscriptionManager != null) {
             final List<SubscriptionInfo> subInfo =
@@ -259,145 +293,280 @@
                 for (SubscriptionInfo sub : subInfo) {
                     final String phoneNumber = getNormalizedNumber(sub);
                     subId2phone.append(sub.getSubscriptionId(), phoneNumber);
-                    phone2subId.put(phoneNumber, sub.getSubscriptionId());
+                    sPhone2subId.put(phoneNumber, sub.getSubscriptionId());
                 }
             }
         }
 
         mSmsProvider = new SmsProvider();
-        mMmsProvider = new MmsProvider();
-        mMmsSmsProvider = new MmsSmsProvider();
-
-        attachAndCreateProviders();
-    }
-
-    private void attachAndCreateProviders() {
         mSmsProvider.attachInfo(this, null);
         mSmsProvider.onCreate();
 
+        mMmsProvider = new MmsProvider();
         mMmsProvider.attachInfo(this, null);
         mMmsProvider.onCreate();
 
+        mMmsSmsProvider = new MmsSmsProvider();
         mMmsSmsProvider.attachInfo(this, null);
         mMmsSmsProvider.onCreate();
+
+        sCacheRecipientsByThread = null;
+        sCacheGetOrCreateThreadId = null;
+    }
+
+    @VisibleForTesting
+    void setProviders(ContentProvider smsProvider,
+                      ContentProvider mmsProvider,
+                      ContentProvider mmsSmsProvider) {
+        mSmsProvider = smsProvider;
+        mMmsProvider = mmsProvider;
+        mMmsSmsProvider = mmsSmsProvider;
     }
 
     @Override
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
-        mBytesOverQuota = getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE).
-                getLong(BYTES_OVER_QUOTA_PREF_KEY, 0);
+        SharedPreferences sharedPreferences = getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE);
+        if (sharedPreferences.getLong(QUOTA_RESET_TIME, Long.MAX_VALUE) <
+                System.currentTimeMillis()) {
+            clearSharedPreferences();
+        }
 
-        backupAllSms(data);
-        backupAllMms(data);
+        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 = mSmsProvider.query(Telephony.Sms.CONTENT_URI, SMS_PROJECTION,
+                        null, null, ORDER_BY_DATE);
+                // Do not backup non text-only MMS's.
+                Cursor mmsCursor = mMmsProvider.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(SMS_BACKUP_FILE_FORMAT, fileNum++));
+                } else {
+                    backupAll(data, mmsCursor, String.format(MMS_BACKUP_FILE_FORMAT, fileNum++));
+                }
+            }
+
+            while (smsCursor != null && !smsCursor.isAfterLast()) {
+                backupAll(data, smsCursor, String.format(SMS_BACKUP_FILE_FORMAT, fileNum++));
+            }
+
+            while (mmsCursor != null && !mmsCursor.isAfterLast()) {
+                backupAll(data, mmsCursor, String.format(MMS_BACKUP_FILE_FORMAT, fileNum++));
+            }
+        }
+    }
+
+    @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) {
-        mBytesOverQuota = (long)((backupDataBytes - quotaBytes)*1.1);
-        getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE).edit()
-                .putLong(BYTES_OVER_QUOTA_PREF_KEY, mBytesOverQuota).apply();
+        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 backupAllSms(FullBackupDataOutput data) throws IOException {
-        try (Cursor cursor = mSmsProvider.query(Telephony.Sms.CONTENT_URI, SMS_PROJECTION, null,
-                null, ORDER_BY_DATE)) {
-            if (DEBUG) {
-                Log.i(TAG, "Backing up SMS");
-            }
-            if (cursor != null) {
-                while (!cursor.isLast() && !cursor.isAfterLast()) {
-                    try (JsonWriter jsonWriter = getJsonWriter(SMS_BACKUP_FILE)) {
-                        putSmsMessagesToJson(cursor, subId2phone, jsonWriter, mMmsSmsProvider,
-                                MAX_MSG_PER_FILE);
-                    }
-                    backupFile(SMS_BACKUP_FILE, data);
-                }
-            }
+    private void backupAll(FullBackupDataOutput data, Cursor cursor, String fileName)
+            throws IOException {
+        if (cursor == null || cursor.isAfterLast()) {
+            return;
         }
-    }
 
-    private void backupAllMms(FullBackupDataOutput data) throws IOException {
-        try (Cursor cursor = mMmsProvider.query(Telephony.Mms.CONTENT_URI, MMS_PROJECTION, null,
-                null, ORDER_BY_DATE)) {
-            if (DEBUG) {
-                Log.i(TAG, "Backing up text MMS");
-            }
-            if (cursor != null) {
-                while (!cursor.isLast() && !cursor.isAfterLast()) {
-                    try (JsonWriter jsonWriter = getJsonWriter(MMS_BACKUP_FILE)) {
-                        putMmsMessagesToJson(cursor, mMmsProvider, mMmsSmsProvider, subId2phone,
-                                jsonWriter, MAX_MSG_PER_FILE);
-                    }
-                    backupFile(MMS_BACKUP_FILE, data);
-                }
+        int messagesWritten = 0;
+        try (JsonWriter jsonWriter = getJsonWriter(fileName)) {
+            if (fileName.endsWith(SMS_BACKUP_FILE_SUFFIX)) {
+                messagesWritten = putSmsMessagesToJson(cursor, mMmsSmsProvider,
+                        subId2phone, jsonWriter, MAX_MSG_PER_FILE);
+            } else {
+                messagesWritten = putMmsMessagesToJson(cursor, mMmsProvider,
+                        mMmsSmsProvider, subId2phone, jsonWriter, MAX_MSG_PER_FILE);
             }
         }
+        backupFile(messagesWritten, fileName, data);
     }
 
     @VisibleForTesting
-    static void putMmsMessagesToJson(Cursor cursor, ContentProvider mmsProvider,
-                                     ContentProvider threadProvider,
-                                     SparseArray<String> subId2phone, JsonWriter jsonWriter,
-                                     int maxMsgPerFile) throws IOException {
+    static int putMmsMessagesToJson(Cursor cursor, ContentProvider mmsProvider,
+                                    ContentProvider threadProvider,
+                                    SparseArray<String> subId2phone, JsonWriter jsonWriter,
+                                    int maxMsgPerFile) throws IOException {
         jsonWriter.beginArray();
-        for (int msgCount=0; msgCount<maxMsgPerFile && cursor.moveToNext();) {
+        int msgCount;
+        for (msgCount = 0; msgCount < maxMsgPerFile && !cursor.isAfterLast(); cursor.moveToNext()) {
             msgCount +=
                     writeMmsToWriter(cursor, mmsProvider, threadProvider, subId2phone, jsonWriter);
         }
         jsonWriter.endArray();
+        return msgCount;
     }
 
     @VisibleForTesting
-    static void putSmsMessagesToJson(Cursor cursor, SparseArray<String> subId2phone,
-                                     JsonWriter jsonWriter, ContentProvider threadProvider,
-                                     int maxMsgPerFile) throws IOException {
+    static int putSmsMessagesToJson(Cursor cursor, ContentProvider threadProvider,
+                                    SparseArray<String> subId2phone, JsonWriter jsonWriter,
+                                    int maxMsgPerFile) throws IOException {
 
         jsonWriter.beginArray();
-        for (int msgCount=0; msgCount<maxMsgPerFile && cursor.moveToNext(); ++msgCount) {
+        int msgCount;
+        for (msgCount = 0; msgCount < maxMsgPerFile && !cursor.isAfterLast();
+                ++msgCount, cursor.moveToNext()) {
             writeSmsToWriter(jsonWriter, cursor, threadProvider, subId2phone);
         }
         jsonWriter.endArray();
+        return msgCount;
     }
 
-    private void backupFile(String fileName, FullBackupDataOutput data) {
+    private void backupFile(int messagesWritten, String fileName, FullBackupDataOutput data)
+            throws IOException {
         final File file = new File(getFilesDir().getPath() + "/" + fileName);
         try {
-            if (mBytesOverQuota > 0) {
-                mBytesOverQuota -= file.length();
-                return;
+            if (messagesWritten > 0) {
+                if (mBytesOverQuota > 0) {
+                    mBytesOverQuota -= file.length();
+                    return;
+                }
+                super.fullBackupFile(file, data);
             }
-            super.fullBackupFile(file, data);
         } finally {
             file.delete();
         }
     }
 
-    @Override
-    public void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type,
-                              long mode, long mtime) throws IOException {
-        if (DEBUG) {
-            Log.i(TAG, "Restoring file " + destination.getName());
+    public static class DeferredSmsMmsRestoreService extends IntentService {
+        private static final String TAG = "DeferredSmsMmsRestoreService";
+
+        private ContentProvider mSmsProvider, mMmsProvider, mMmsSmsProvider;
+
+        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);
         }
 
-        if (destination.getName().equals(SMS_BACKUP_FILE)) {
-            if (DEBUG) {
-                Log.i(TAG, "Restoring SMS");
+        @Override
+        protected void onHandleIntent(Intent intent) {
+            File[] files = 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);
+                }
+            });
+
+            if (files == null) {
+                return;
             }
-            try (JsonReader jsonReader = getJsonReader(data.getFileDescriptor())) {
-                putSmsMessagesToProvider(jsonReader, mSmsProvider, mMmsSmsProvider, phone2subId);
+            Arrays.sort(files, mFileComparator);
+
+            for (File file : files) {
+                final String fileName = file.getName();
+                try (FileInputStream fileInputStream = new FileInputStream(file)) {
+                    TelephonyBackupAgent.doRestoreFile(fileName, fileInputStream.getFD(),
+                            mSmsProvider, mMmsProvider, mMmsSmsProvider);
+                    file.delete();
+                } catch (IOException e) {
+                    if (DEBUG) {
+                        Log.e(TAG, e.toString());
+                    }
+                }
             }
-        } else if (destination.getName().equals(MMS_BACKUP_FILE)) {
-            if (DEBUG) {
-                Log.i(TAG, "Restoring text MMS");
-            }
-            try (JsonReader jsonReader = getJsonReader(data.getFileDescriptor())) {
-                putMmsMessagesToProvider(jsonReader, mMmsProvider, mMmsSmsProvider, phone2subId);
-            }
-        } else {
-            super.onRestoreFile(data, size, destination, type, mode, mtime);
         }
+
+        @Override
+        public void onCreate() {
+            super.onCreate();
+            mSmsProvider = new SmsProvider();
+            mSmsProvider.attachInfo(this, null);
+            mSmsProvider.onCreate();
+
+            mMmsProvider = new MmsProvider();
+            mMmsProvider.attachInfo(this, null);
+            mMmsProvider.onCreate();
+
+            mMmsSmsProvider = new MmsSmsProvider();
+            mMmsSmsProvider.attachInfo(this, null);
+            mMmsSmsProvider.onCreate();
+        }
+
+        public static Intent getIntent(Context context) {
+            return new Intent(context, DeferredSmsMmsRestoreService.class);
+        }
+    }
+
+    @Override
+    public void onRestoreFinished() {
+        super.onRestoreFinished();
+        startService(DeferredSmsMmsRestoreService.getIntent(this));
+    }
+
+    private static void doRestoreFile(String fileName, FileDescriptor fd,
+                                      ContentProvider smsProvider, ContentProvider mmsProvider,
+                                      ContentProvider mmsSmsProvider) throws IOException {
         if (DEBUG) {
-            Log.i(TAG, "Finished restore");
+            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, smsProvider, mmsSmsProvider, sPhone2subId);
+            } else if (fileName.endsWith(MMS_BACKUP_FILE_SUFFIX)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Restoring text MMS");
+                }
+                putMmsMessagesToProvider(jsonReader, mmsProvider, mmsSmsProvider, sPhone2subId);
+            } else {
+                if (DEBUG) {
+                    Log.e(TAG, "Unknown file to restore:" + fileName);
+                }
+            }
         }
     }
 
@@ -406,16 +575,22 @@
                                          ContentProvider threadProvider,
                                          Map<String, Integer> phone2subId) throws IOException {
         jsonReader.beginArray();
+        int msgCount = 0;
+        final int bulkInsertSize = MAX_MSG_PER_FILE;
+        ContentValues[] values = new ContentValues[bulkInsertSize];
         while (jsonReader.hasNext()) {
-            ContentValues smsValues =
-                    readSmsValuesFromReader(jsonReader, threadProvider, phone2subId);
-            if (doesSmsExist(smsProvider, smsValues)) {
-                if (DEBUG) {
-                    Log.e(TAG, String.format("Sms: %s already exists", smsValues.toString()));
-                }
+            ContentValues cv = readSmsValuesFromReader(jsonReader, threadProvider, phone2subId);
+            if (doesSmsExist(smsProvider, cv)) {
                 continue;
             }
-            smsProvider.insert(Telephony.Sms.CONTENT_URI, smsValues);
+            values[(msgCount++) % bulkInsertSize] = cv;
+            if (msgCount % bulkInsertSize == 0) {
+                smsProvider.bulkInsert(Telephony.Sms.CONTENT_URI, values);
+            }
+        }
+        if (msgCount % bulkInsertSize > 0) {
+            smsProvider.bulkInsert(Telephony.Sms.CONTENT_URI,
+                    Arrays.copyOf(values, msgCount % bulkInsertSize));
         }
         jsonReader.endArray();
     }
@@ -439,6 +614,7 @@
 
     @VisibleForTesting
     static final String[] PROJECTION_ID = {BaseColumns._ID};
+    private static final int ID_IDX = 0;
 
     private static boolean doesSmsExist(ContentProvider smsProvider, ContentValues smsValues) {
         final String where = String.format("%s = %d and %s = %s",
@@ -458,7 +634,7 @@
                 null, null)) {
             if (cursor != null && cursor.moveToFirst()) {
                 do {
-                    final int mmsId = cursor.getInt(0);
+                    final int mmsId = cursor.getInt(ID_IDX);
                     final MmsBody body = getMmsBody(mmsProvider, mmsId);
                     if (body != null && body.equals(mms.body)) {
                         return true;
@@ -528,8 +704,8 @@
                                                  ContentProvider threadProvider,
                                                  Map<String, Integer> phone2id)
             throws IOException {
-        ContentValues values = new ContentValues(8+defaultValuesSms.size());
-        values.putAll(defaultValuesSms);
+        ContentValues values = new ContentValues(8+sDefaultValuesSms.size());
+        values.putAll(sDefaultValuesSms);
         jsonReader.beginObject();
         while (jsonReader.hasNext()) {
             String name = jsonReader.nextName();
@@ -579,11 +755,7 @@
                                         ContentProvider threadProvider,
                                         SparseArray<String> subId2phone,
                                         JsonWriter jsonWriter) throws IOException {
-        // Do not backup non text-only MMS's.
-        if (cursor.getInt(cursor.getColumnIndex(Telephony.Mms.TEXT_ONLY)) != 1) {
-            return 0;
-        }
-        final int mmsId = cursor.getInt(0);
+        final int mmsId = cursor.getInt(ID_IDX);
         final MmsBody body = getMmsBody(mmsProvider, mmsId);
         if (body == null || body.text == null) {
             return 0;
@@ -611,7 +783,6 @@
                             getRecipientsByThread(threadProvider, threadId));
                     break;
                 case Telephony.Mms._ID:
-                case Telephony.Mms.TEXT_ONLY:
                 case Telephony.Mms.SUBJECT_CHARSET:
                     break;
                 case Telephony.Mms.SUBJECT:
@@ -639,8 +810,8 @@
     private static Mms readMmsFromReader(JsonReader jsonReader, ContentProvider threadProvider,
                                          Map<String, Integer> phone2id) throws IOException {
         Mms mms = new Mms();
-        mms.values = new ContentValues(6+defaultValuesMms.size());
-        mms.values.putAll(defaultValuesMms);
+        mms.values = new ContentValues(6+sDefaultValuesMms.size());
+        mms.values.putAll(sDefaultValuesMms);
         jsonReader.beginObject();
         String bodyText = null;
         int bodyCharset = CharacterSets.DEFAULT_CHARSET;
@@ -707,14 +878,13 @@
         int charSet = 0;
 
         try (Cursor cursor = mmsProvider.query(MMS_PART_CONTENT_URI, MMS_TEXT_PROJECTION,
-                null, null/*selectionArgs*/, ORDER_BY_ID)) {
+                Telephony.Mms.Part.CONTENT_TYPE + "=?", new String[]{ContentType.TEXT_PLAIN},
+                ORDER_BY_ID)) {
             if (cursor != null && cursor.moveToFirst()) {
                 do {
-                    if (ContentType.TEXT_PLAIN.equals(cursor.getString(0))) {
-                        body = (body == null ? cursor.getString(1)
-                                             : body.concat(cursor.getString(1)));
-                        charSet = cursor.getInt(2);
-                    }
+                    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());
             }
         }
@@ -752,7 +922,7 @@
         jsonReader.beginArray();
         while (jsonReader.hasNext()) {
             jsonReader.beginObject();
-            ContentValues addrValues = new ContentValues(defaultValuesAddr);
+            ContentValues addrValues = new ContentValues(sDefaultValuesAddr);
             while (jsonReader.hasNext()) {
                 final String name = jsonReader.nextName();
                 switch (name) {
@@ -887,11 +1057,12 @@
     }
 
     private JsonWriter getJsonWriter(final String fileName) throws IOException {
-        return new JsonWriter(new OutputStreamWriter(new DeflaterOutputStream(
-                openFileOutput(fileName, MODE_PRIVATE)), CHARSET_UTF8));
+        return new JsonWriter(new BufferedWriter(new OutputStreamWriter(new DeflaterOutputStream(
+                openFileOutput(fileName, MODE_PRIVATE)), CHARSET_UTF8), WRITER_BUFFER_SIZE));
     }
 
-    private JsonReader getJsonReader(final FileDescriptor fileDescriptor) throws IOException {
+    private static JsonReader getJsonReader(final FileDescriptor fileDescriptor)
+            throws IOException {
         return new JsonReader(new InputStreamReader(new InflaterInputStream(
                 new FileInputStream(fileDescriptor)), CHARSET_UTF8));
     }
@@ -912,50 +1083,21 @@
         }
     }
 
-    // Copied from packages/apps/Messaging/src/com/android/messaging/sms/DatabaseMessages.java.
-    /**
-     * Decoded string by character set
-     */
-    private static String getDecodedString(final byte[] data, final int charset)  {
-        if (CharacterSets.ANY_CHARSET == charset) {
-            return new String(data); // system default encoding.
-        } else {
-            try {
-                final String name = CharacterSets.getMimeName(charset);
-                return new String(data, name);
-            } catch (final UnsupportedEncodingException e) {
-                try {
-                    return new String(data, CharacterSets.MIMENAME_ISO_8859_1);
-                } catch (final UnsupportedEncodingException exception) {
-                    return new String(data); // system default encoding.
-                }
-            }
-        }
-    }
-
-    // Copied from packages/apps/Messaging/src/com/android/messaging/sms/DatabaseMessages.java.
-    /**
-     * Unpack a given String into a byte[].
-     */
-    private static byte[] getStringBytes(final String data, final int charset) {
-        if (CharacterSets.ANY_CHARSET == charset) {
-            return data.getBytes();
-        } else {
-            try {
-                final String name = CharacterSets.getMimeName(charset);
-                return data.getBytes(name);
-            } catch (final UnsupportedEncodingException e) {
-                return data.getBytes();
-            }
-        }
-    }
-
     @VisibleForTesting
     static final Uri THREAD_ID_CONTENT_URI = Uri.parse("content://mms-sms/threadID");
+
     // Copied from frameworks/opt/telephony/src/java/android/provider/Telephony.java because we
     // can't use ContentResolver during backup/restore.
     private static long getOrCreateThreadId(
             ContentProvider contentProvider, Set<String> recipients) {
+        if (sCacheGetOrCreateThreadId == null) {
+            sCacheGetOrCreateThreadId = new HashMap<>();
+        }
+
+        if (sCacheGetOrCreateThreadId.containsKey(recipients)) {
+            return sCacheGetOrCreateThreadId.get(recipients);
+        }
+
         Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
 
         for (String recipient : recipients) {
@@ -971,25 +1113,40 @@
         try (Cursor cursor = contentProvider.query(uri, PROJECTION_ID, null, null, null)) {
             if (cursor != null) {
                 if (cursor.moveToFirst()) {
-                    return cursor.getLong(0);
+                    final long threadId = cursor.getLong(ID_IDX);
+                    sCacheGetOrCreateThreadId.put(recipients, threadId);
+                    return threadId;
                 } else {
-                    Log.e(TAG, "getOrCreateThreadId returned no rows!");
+                    if (DEBUG) {
+                        Log.e(TAG, "getOrCreateThreadId returned no rows!");
+                    }
                 }
             }
         }
 
-        Log.e(TAG, "getOrCreateThreadId failed with " + recipients.size() + " recipients");
+        if (DEBUG) {
+            Log.e(TAG, "getOrCreateThreadId failed with " + recipients.size() + " recipients");
+        }
         throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
     }
 
-    // Copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java.
+    // Mostly copied from packages/apps/Messaging/src/com/android/messaging/sms/MmsUtils.java.
     private static List<String> getRecipientsByThread(final ContentProvider threadProvider,
                                                       final long threadId) {
-        final String spaceSepIds = getRawRecipientIdsForThread(threadProvider, threadId);
-        if (!TextUtils.isEmpty(spaceSepIds)) {
-            return getAddresses(threadProvider, spaceSepIds);
+        if (sCacheRecipientsByThread == null) {
+            sCacheRecipientsByThread = new HashMap<>();
         }
-        return null;
+
+        if (!sCacheRecipientsByThread.containsKey(threadId)) {
+            final String spaceSepIds = getRawRecipientIdsForThread(threadProvider, threadId);
+            if (!TextUtils.isEmpty(spaceSepIds)) {
+                sCacheRecipientsByThread.put(threadId, getAddresses(threadProvider, spaceSepIds));
+            } else {
+                sCacheRecipientsByThread.put(threadId, new ArrayList<String>());
+            }
+        }
+
+        return sCacheRecipientsByThread.get(threadId);
     }
 
     @VisibleForTesting
diff --git a/tests/Android.mk b/tests/Android.mk
index 1041716..fc12378 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
 
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 
@@ -14,6 +14,4 @@
 
 LOCAL_INSTRUMENTATION_FOR := TelephonyProvider
 
-LOCAL_SDK_VERSION := current
-
 include $(BUILD_PACKAGE)
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index 1dcb3db..288b36b 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -17,6 +17,7 @@
 package com.android.providers.telephony;
 
 import android.annotation.TargetApi;
+import android.app.backup.FullBackupDataOutput;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -64,8 +65,6 @@
     private FakeCursor mSmsCursor, mMmsCursor;
     /* Test data with sms and mms */
     private ContentValues[] mSmsRows, mMmsRows;
-    /* Non-text mms for testing it is not being backed up */
-    private ContentValues mMmsNonText;
     /* Json representation for the test data */
     private String[] mSmsJson, mMmsJson;
     /* sms, mms json concatenated as json array */
@@ -81,10 +80,17 @@
     private ThreadProvider mThreadProvider = new ThreadProvider();
 
     private static final String EMPTY_JSON_ARRAY = "[]";
+    private int mStoredMaxMsgPerFile;
+
+    TelephonyBackupAgent mTelephonyBackupAgent;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
+        mTelephonyBackupAgent = new TelephonyBackupAgent();
+        mTelephonyBackupAgent.attach(getContext());
+
         /* Filling up subscription maps */
         mStringWriter = new StringWriter();
         mSubId2Phone = new SparseArray<String>();
@@ -96,11 +102,13 @@
             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);
 
-        mCursors = new HashMap<Uri, FakeCursor>();
 
         /* Generating test data */
         mSmsRows = new ContentValues[3];
@@ -191,14 +199,6 @@
                 "\"sub_cs\":\"10\"}";
         mAllMmsJson = makeJsonArray(mMmsJson);
 
-
-        // Should not be backed up. Cause flag text_only is false.
-        mMmsNonText = createMmsRow(10 /*id*/, 3 /*subid*/, "Subject 10" /*subject*/,
-                10 /*subcharset*/,
-                111133 /*date*/, 1111132 /*datesent*/, 5 /*type*/, 19 /*version*/, 0 /*textonly*/,
-                333 /*msgBox*/, null /*contentLocation*/, "MMs body 3" /*body*/,
-                131 /*body charset*/, new String[]{"+8888888888"} /*addresses*/, 3 /*threadId*/);
-
         mContentProvider = new MockContentProvider() {
             @Override
             public Cursor query(Uri uri, String[] projection, String selection,
@@ -208,12 +208,24 @@
                     if (projection != null) {
                         fakeCursor.setProjection(projection);
                     }
+                    fakeCursor.nextRow = 0;
                     return fakeCursor;
                 }
                 return super.query(uri, projection, selection, selectionArgs, sortOrder);
             }
         };
 
+        mStoredMaxMsgPerFile = TelephonyBackupAgent.MAX_MSG_PER_FILE;
+        TelephonyBackupAgent.sCacheGetOrCreateThreadId = null;
+        TelephonyBackupAgent.sCacheRecipientsByThread = null;
+        mTelephonyBackupAgent.clearSharedPreferences();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        TelephonyBackupAgent.MAX_MSG_PER_FILE = mStoredMaxMsgPerFile;
+        mTelephonyBackupAgent.clearSharedPreferences();
+        super.tearDown();
     }
 
     private static String makeJsonArray(String[] json) {
@@ -293,7 +305,6 @@
     // 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);
@@ -305,7 +316,6 @@
         smilPart.put(Telephony.Mms.Part.CONTENT_ID, "<smil>");
         smilPart.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml");
         smilPart.put(Telephony.Mms.Part.TEXT, smil);
-        table.add(smilPart); // This part should not be backed up.
         mMmsAllContentValues.add(smilPart);
 
         final ContentValues bodyPart = new ContentValues();
@@ -341,8 +351,8 @@
      * @throws Exception
      */
     public void testBackupSms_NoSms() throws Exception {
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), new ThreadProvider(), 1);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new ThreadProvider(), mSubId2Phone,
+                new JsonWriter(mStringWriter), 1);
         assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
     }
 
@@ -352,8 +362,8 @@
      */
     public void testBackupSms_AllSms() throws Exception {
         mSmsTable.addAll(Arrays.asList(mSmsRows));
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), mThreadProvider, 4);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mThreadProvider, mSubId2Phone,
+                new JsonWriter(mStringWriter), 4);
         assertEquals(mAllSmsJson, mStringWriter.toString());
     }
 
@@ -363,8 +373,8 @@
      */
     public void testBackupSms_AllSmsWithExactFileLimit() throws Exception {
         mSmsTable.addAll(Arrays.asList(mSmsRows));
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), mThreadProvider, 3);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mThreadProvider, mSubId2Phone,
+                new JsonWriter(mStringWriter), 3);
         assertEquals(mAllSmsJson, mStringWriter.toString());
     }
 
@@ -375,18 +385,18 @@
     public void testBackupSms_AllSmsOneMessagePerFile() throws Exception {
         mSmsTable.addAll(Arrays.asList(mSmsRows));
 
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), mThreadProvider, 1);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mThreadProvider, mSubId2Phone,
+                new JsonWriter(mStringWriter), 1);
         assertEquals("[" + mSmsJson[0] + "]", mStringWriter.toString());
 
         mStringWriter = new StringWriter();
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), mThreadProvider, 1);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mThreadProvider, mSubId2Phone,
+                new JsonWriter(mStringWriter), 1);
         assertEquals("[" + mSmsJson[1] + "]", mStringWriter.toString());
 
         mStringWriter = new StringWriter();
-        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mSubId2Phone,
-                new JsonWriter(mStringWriter), mThreadProvider, 1);
+        TelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, mThreadProvider, mSubId2Phone,
+                new JsonWriter(mStringWriter), 1);
         assertEquals("[" + mSmsJson[2] + "]", mStringWriter.toString());
     }
 
@@ -406,7 +416,6 @@
      */
     public void testBackupMms_AllMms() throws Exception {
         mMmsTable.addAll(Arrays.asList(mMmsRows));
-        mMmsTable.add(mMmsNonText);
         TelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, mContentProvider,
                 mThreadProvider, mSubId2Phone, new JsonWriter(mStringWriter), 4);
         assertEquals(mAllMmsJson, mStringWriter.toString());
@@ -481,6 +490,38 @@
     }
 
     /**
+     * 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 {
+        TelephonyBackupAgent.MAX_MSG_PER_FILE = 1;
+        final int backupSize = 6144;
+        final int backupSizeAfterFirstQuotaHit = 5120;
+        final int backupSizeAfterSecondQuotaHit = 4096;
+
+        mSmsTable.addAll(Arrays.asList(mSmsRows));
+        mMmsTable.addAll(Arrays.asList(mMmsRows));
+        mTelephonyBackupAgent.setProviders(mContentProvider, mContentProvider, mThreadProvider);
+
+        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());
+    }
+
+    /**
      * Test restore sms with three mms json object in the array.
      * @throws Exception
      */
@@ -520,6 +561,14 @@
         }
 
         @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;
@@ -703,7 +752,7 @@
     private static class FakeCursor extends MockCursor {
         String[] projection;
         List<ContentValues> rows;
-        int nextRow = -1;
+        int nextRow = 0;
 
         public FakeCursor(List<ContentValues> rows, String[] projection) {
             this.projection = projection;