merge in nyc-release history after reset to nyc-dev
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index 5f4acaa..7a9d701 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -160,6 +160,14 @@
     // Order by Date entries from database. We start backup from the oldest.
     private static final String ORDER_BY_DATE = "date ASC";
 
+    // This is a hard coded string rather than a localized one because we don't want it to
+    // change when you change locale.
+    @VisibleForTesting
+    static final String UNKNOWN_SENDER = "\u02BCUNKNOWN_SENDER!\u02BC";
+
+    // Thread id for UNKNOWN_SENDER.
+    private long mUnknownSenderThreadId;
+
     // Columns from SMS database for backup/restore.
     @VisibleForTesting
     static final String[] SMS_PROJECTION = new String[] {
@@ -230,10 +238,9 @@
     @VisibleForTesting
     int mMaxMsgPerFile = 1000;
 
-
     // Default values for SMS, MMS, Addresses restore.
-    private static final ContentValues sDefaultValuesSms = new ContentValues(3);
-    private static final ContentValues sDefaultValuesMms = new ContentValues(5);
+    private static ContentValues sDefaultValuesSms = new ContentValues(5);
+    private static ContentValues sDefaultValuesMms = new ContentValues(6);
     private static final ContentValues sDefaultValuesAddr = new ContentValues(2);
 
     // Shared preferences for the backup agent.
@@ -252,6 +259,7 @@
         // Consider restored messages read and seen.
         sDefaultValuesSms.put(Telephony.Sms.READ, 1);
         sDefaultValuesSms.put(Telephony.Sms.SEEN, 1);
+        sDefaultValuesSms.put(Telephony.Sms.ADDRESS, UNKNOWN_SENDER);
         // If there is no sub_id with self phone number on restore set it to -1.
         sDefaultValuesSms.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
 
@@ -298,6 +306,7 @@
             }
         }
         mContentResolver = getContentResolver();
+        initUnknownSender();
     }
 
     @VisibleForTesting
@@ -310,6 +319,13 @@
         mPhone2subId = phone2subId;
     }
 
+    @VisibleForTesting
+    void initUnknownSender() {
+        mUnknownSenderThreadId = getOrCreateThreadId(null);
+        sDefaultValuesSms.put(Telephony.Sms.THREAD_ID, mUnknownSenderThreadId);
+        sDefaultValuesMms.put(Telephony.Mms.THREAD_ID, mUnknownSenderThreadId);
+    }
+
     @Override
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         SharedPreferences sharedPreferences = getSharedPreferences(BACKUP_PREFS, MODE_PRIVATE);
@@ -348,18 +364,22 @@
                 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++));
+                    backupAll(data, smsCursor,
+                            String.format(Locale.US, SMS_BACKUP_FILE_FORMAT, fileNum++));
                 } else {
-                    backupAll(data, mmsCursor, String.format(MMS_BACKUP_FILE_FORMAT, fileNum++));
+                    backupAll(data, mmsCursor, String.format(Locale.US,
+                            MMS_BACKUP_FILE_FORMAT, fileNum++));
                 }
             }
 
             while (smsCursor != null && !smsCursor.isAfterLast()) {
-                backupAll(data, smsCursor, String.format(SMS_BACKUP_FILE_FORMAT, fileNum++));
+                backupAll(data, smsCursor,
+                        String.format(Locale.US, SMS_BACKUP_FILE_FORMAT, fileNum++));
             }
 
             while (mmsCursor != null && !mmsCursor.isAfterLast()) {
-                backupAll(data, mmsCursor, String.format(MMS_BACKUP_FILE_FORMAT, fileNum++));
+                backupAll(data, mmsCursor,
+                        String.format(Locale.US, MMS_BACKUP_FILE_FORMAT, fileNum++));
             }
         }
 
@@ -487,11 +507,11 @@
                     final String fileName = file.getName();
                     try (FileInputStream fileInputStream = new FileInputStream(file)) {
                         mTelephonyBackupAgent.doRestoreFile(fileName, fileInputStream.getFD());
+                    } catch (Exception e) {
+                        // Either IOException or RuntimeException.
+                        Log.e(TAG, e.toString());
+                    } finally {
                         file.delete();
-                    } catch (IOException e) {
-                        if (DEBUG) {
-                            Log.e(TAG, e.toString());
-                        }
                     }
                 }
             } finally {
@@ -611,7 +631,7 @@
     private static final int ID_IDX = 0;
 
     private boolean doesSmsExist(ContentValues smsValues) {
-        final String where = String.format("%s = %d and %s = %s",
+        final String where = String.format(Locale.US, "%s = %d and %s = %s",
                 Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE),
                 Telephony.Sms.BODY,
                 DatabaseUtils.sqlEscapeString(smsValues.getAsString(Telephony.Sms.BODY)));
@@ -622,7 +642,7 @@
     }
 
     private boolean doesMmsExist(Mms mms) {
-        final String where = String.format("%s = %d",
+        final String where = String.format(Locale.US, "%s = %d",
                 Telephony.Sms.DATE, mms.values.getAsLong(Telephony.Mms.DATE));
         try (Cursor cursor = mContentResolver.query(Telephony.Mms.CONTENT_URI, PROJECTION_ID, where,
                 null, null)) {
@@ -680,8 +700,12 @@
     }
 
     private void handleThreadId(JsonWriter jsonWriter, long threadId) throws IOException {
-        writeRecipientsToWriter(jsonWriter.name(RECIPIENTS),
-                getRecipientsByThread(threadId));
+        final List<String> recipients = getRecipientsByThread(threadId);
+        if (recipients == null || recipients.isEmpty()) {
+            return;
+        }
+
+        writeRecipientsToWriter(jsonWriter.name(RECIPIENTS), recipients);
         if (!mThreadArchived.containsKey(threadId)) {
             boolean isArchived = isThreadArchived(threadId);
             if (isArchived) {
@@ -702,7 +726,7 @@
 
         try (Cursor cursor = getContentResolver().query(uri, THREAD_ARCHIVED_PROJECTION, null, null,
                 null)) {
-            if (cursor.moveToFirst()) {
+            if (cursor != null && cursor.moveToFirst()) {
                 return cursor.getInt(THREAD_ARCHIVED_IDX) == 1;
             }
         }
@@ -722,7 +746,7 @@
 
     private ContentValues readSmsValuesFromReader(JsonReader jsonReader)
             throws IOException {
-        ContentValues values = new ContentValues(8+sDefaultValuesSms.size());
+        ContentValues values = new ContentValues(6+sDefaultValuesSms.size());
         values.putAll(sDefaultValuesSms);
         long threadId = -1;
         boolean isArchived = false;
@@ -829,7 +853,7 @@
 
     private Mms readMmsFromReader(JsonReader jsonReader) throws IOException {
         Mms mms = new Mms();
-        mms.values = new ContentValues(6+sDefaultValuesMms.size());
+        mms.values = new ContentValues(5+sDefaultValuesMms.size());
         mms.values.putAll(sDefaultValuesMms);
         jsonReader.beginObject();
         String bodyText = null;
@@ -1002,7 +1026,7 @@
         final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon()
                 .appendPath(String.valueOf(dummyId)).appendPath("part").build();
 
-        final String srcName = String.format("text.%06d.txt", 0);
+        final String srcName = String.format(Locale.US, "text.%06d.txt", 0);
         { // Insert SMIL part.
             final String smilBody = String.format(sSmilTextPart, srcName);
             final String smil = String.format(sSmilTextOnly, smilBody);
@@ -1129,13 +1153,29 @@
     }
 
     private long getOrCreateThreadId(Set<String> recipients) {
+        if (recipients == null) {
+            recipients = new ArraySet<String>();
+        }
+
+        if (recipients.isEmpty()) {
+            recipients.add(UNKNOWN_SENDER);
+        }
+
         if (mCacheGetOrCreateThreadId == null) {
             mCacheGetOrCreateThreadId = new HashMap<>();
         }
 
         if (!mCacheGetOrCreateThreadId.containsKey(recipients)) {
-            mCacheGetOrCreateThreadId.put(recipients,
-                    Telephony.Threads.getOrCreateThreadId(this, recipients));
+            long threadId = mUnknownSenderThreadId;
+            try {
+                threadId = Telephony.Threads.getOrCreateThreadId(this, recipients);
+            } catch (RuntimeException e) {
+                if (DEBUG) {
+                    Log.e(TAG, e.toString());
+                }
+            }
+            mCacheGetOrCreateThreadId.put(recipients, threadId);
+            return threadId;
         }
 
         return mCacheGetOrCreateThreadId.get(recipients);
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index 5869a84..4866dbd 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -36,7 +36,6 @@
 import android.util.ArraySet;
 import android.util.JsonReader;
 import android.util.JsonWriter;
-import android.util.Log;
 import android.util.SparseArray;
 
 import org.json.JSONArray;
@@ -119,8 +118,8 @@
 
 
         /* Generating test data */
-        mSmsRows = new ContentValues[3];
-        mSmsJson = new String[3];
+        mSmsRows = new ContentValues[4];
+        mSmsJson = new String[4];
         mSmsRows[0] = createSmsRow(1, 1, "+1232132214124", "sms 1", "sms subject", 9087978987l,
                 999999999, 3, 44, 1);
         mSmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"address\":" +
@@ -145,6 +144,14 @@
                 "\"recipients\":[\"+1232221412433\",\"+1232221412444\"]}";
         mThreadProvider.getOrCreateThreadId(new String[]{"+1232221412433", "+1232221412444"});
 
+
+        mSmsRows[3] = createSmsRow(5, 3, null, "sms 4", null,
+                111111111111l, 999999999, 2, 3, 5);
+        mSmsJson[3] = "{\"self_phone\":\"+333333333333333\"," +
+                "\"body\":\"sms 4\",\"date\":\"111111111111\"," +
+                "\"date_sent\":" +
+                "\"999999999\",\"status\":\"2\",\"type\":\"3\"}";
+
         mAllSmsJson = makeJsonArray(mSmsJson);
 
 
@@ -392,7 +399,7 @@
      * @throws Exception
      */
     public void testBackupSms_AllSmsWithExactFileLimit() throws Exception {
-        mTelephonyBackupAgent.mMaxMsgPerFile = 3;
+        mTelephonyBackupAgent.mMaxMsgPerFile = 4;
         mSmsTable.addAll(Arrays.asList(mSmsRows));
         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
         assertEquals(mAllSmsJson, mStringWriter.toString());
@@ -416,6 +423,10 @@
         mStringWriter = new StringWriter();
         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
         assertEquals("[" + mSmsJson[2] + "]", mStringWriter.toString());
+
+        mStringWriter = new StringWriter();
+        mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
+        assertEquals("[" + mSmsJson[3] + "]", mStringWriter.toString());
     }
 
     /**
@@ -485,6 +496,7 @@
      * @throws Exception
      */
     public void testRestoreSms_AllSms() throws Exception {
+        mTelephonyBackupAgent.initUnknownSender();
         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
         FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows);
         mMockContentResolver.addProvider("sms", smsProvider);
@@ -526,9 +538,9 @@
      */
     public void testBackup_WithQuotaExceeded() throws Exception {
         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
-        final int backupSize = 6144;
-        final int backupSizeAfterFirstQuotaHit = 5120;
-        final int backupSizeAfterSecondQuotaHit = 4096;
+        final int backupSize = 7168;
+        final int backupSizeAfterFirstQuotaHit = 6144;
+        final int backupSizeAfterSecondQuotaHit = 5120;
 
         mSmsTable.addAll(Arrays.asList(mSmsRows));
         mMmsTable.addAll(Arrays.asList(mMmsRows));
@@ -584,6 +596,10 @@
                 modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
             }
 
+            if (modifiedValues.get(Telephony.Sms.ADDRESS) == null) {
+                modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
+            }
+
             assertEquals(modifiedValues, values);
             return null;
         }
@@ -712,6 +728,10 @@
 
 
         public int getOrCreateThreadId(final String[] recipients) {
+            if (recipients == null || recipients.length == 0) {
+                throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+            }
+
             Set<Integer> ids = new ArraySet<>();
             for (String rec : recipients) {
                 if (!id2Recipient.contains(rec)) {
@@ -730,6 +750,10 @@
         }
 
         private String getSpaceSepIds(int threadId) {
+            if (id2Thread.size() < threadId) {
+                return null;
+            }
+
             String spaceSepIds = null;
             for (Integer id : id2Thread.get(threadId-1)) {
                 spaceSepIds = (spaceSepIds == null ? "" : spaceSepIds + " ") + String.valueOf(id);