Merge branch 'dev/11/fp3/security-aosp-rvc-release' into int/11/fp3

* dev/11/fp3/security-aosp-rvc-release:
  Update file permissions using canonical path

Change-Id: Id6614db08056cc12ee46fb83d5bdb7973294cdac
diff --git a/Android.bp b/Android.bp
index def7cde..423fa07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6,7 +6,11 @@
     platform_apis: true,
     certificate: "platform",
     libs: ["telephony-common"],
-    static_libs: ["android-common", "telephonyprovider-protos"],
+    static_libs: [
+    "android-common",
+    "telephonyprovider-protos",
+    "android-support-v4"
+    ],
 }
 
 filegroup {
diff --git a/assets/latest_carrier_id/carrier_list.pb b/assets/latest_carrier_id/carrier_list.pb
index 12ec024..986ccd8 100644
--- a/assets/latest_carrier_id/carrier_list.pb
+++ b/assets/latest_carrier_id/carrier_list.pb
Binary files differ
diff --git a/assets/latest_carrier_id/carrier_list.textpb b/assets/latest_carrier_id/carrier_list.textpb
index 0d8cb87..7c9b4fe 100644
--- a/assets/latest_carrier_id/carrier_list.textpb
+++ b/assets/latest_carrier_id/carrier_list.textpb
Binary files differ
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 309a57b..9bcbd19 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/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">"تهيئة شبكة الجوال"</string>
+    <string name="app_label" product="tablet" msgid="9194799012395299737">"إعداد شبكة الجوال"</string>
     <string name="app_label" product="default" msgid="8338087656149558019">"مساحة تخزين للهاتف والرسائل"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 1c1c927..dc13503 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/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">"Мобайль Сүлжээний Тохируулга"</string>
-    <string name="app_label" product="default" msgid="8338087656149558019">"Гар утас болон Зурвасын Санах ой"</string>
+    <string name="app_label" product="default" msgid="8338087656149558019">"Гар утас болон Мессежийн Санах ой"</string>
 </resources>
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index fade7ce..cda84fd 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.UriMatcher;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -43,18 +44,28 @@
 import android.provider.Telephony.Mms.Rate;
 import android.provider.Telephony.MmsSms;
 import android.provider.Telephony.Threads;
+import android.support.v4.content.FileProvider;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
+import com.google.android.mms.MmsException;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.PduComposer;
+import com.google.android.mms.pdu.RetrieveConf;
 import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduPersister;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.SendReq;
 import com.google.android.mms.util.DownloadDrmHelper;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.HashSet;
 
 /**
  * The class to provide base facility to access MMS related content,
@@ -71,6 +82,15 @@
 
     // The name of parts directory. The full dir is "app_parts".
     static final String PARTS_DIR_NAME = "parts";
+    static final String COLUMN_PDU_PATH = "pdu_path";
+
+    private static final int MAX_FILE_NAME_LENGTH = 30;
+
+    private final static String[] PDU_COLUMNS = new String[] {
+        "_id",
+        "pdu_path",
+        "pdu_data"
+    };
 
     @Override
     public boolean onCreate() {
@@ -90,6 +110,55 @@
         return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU;
     }
 
+    private byte[] getPduDataFromDB(int msgId, int msgType) {
+        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, msgId);
+        PduPersister persister = PduPersister.getPduPersister(getContext());
+        byte[] mmsData = null;
+        try {
+            if (Mms.MESSAGE_BOX_INBOX == msgType
+                    || Mms.MESSAGE_BOX_SENT == msgType) {
+                GenericPdu pdu = persister.load(uri);
+                if (pdu != null) {
+                    mmsData = new PduComposer(getContext(), pdu).make();
+                }
+            }
+        } catch (MmsException e) {
+            Log.e(TAG, "MmsException   e=" + e);
+        }
+        return mmsData;
+    }
+
+    private Cursor getPdus(int itemCount, int dataCount, String[] data) {
+        MatrixCursor cursor = new MatrixCursor(PDU_COLUMNS, 1);
+        long token = Binder.clearCallingIdentity();
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        db.beginTransaction();
+        try {
+            for (int i = 0; i < dataCount; i++) {
+                int msgId = Integer.parseInt(data[i * itemCount]);
+                int msgType = Integer.parseInt(data[i * itemCount + 1]);
+                String pduPath = data[i * itemCount + 2];
+                byte[] pduData = getPduDataFromDB(msgId, msgType);
+                if (pduData == null || pduData.length == 0) {
+                    Log.e(TAG, "can't get msgId:" + msgId + " pdu data.");
+                    continue;
+                }
+                Object[] row = new Object[3];
+                row[0] = msgId;
+                row[1] = pduPath;
+                row[2] = pduData;
+                cursor.addRow(row);
+            }
+            db.setTransactionSuccessful();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception  e =", e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+            db.endTransaction();
+        }
+        return cursor;
+    }
+
     @Override
     public Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
@@ -221,6 +290,19 @@
             case MMS_THREADS:
                 qb.setTables(pduTable + " group by thread_id");
                 break;
+            case MMS_GET_PDU:
+                int itemCount = Integer.parseInt(uri.getQueryParameter("item_count"));
+                int dataCount = Integer.parseInt(uri.getQueryParameter("data_count"));
+                String split = uri.getQueryParameter("data_split");
+                String[] data = null;
+                if (!TextUtils.isEmpty(uri.getQueryParameter("data"))) {
+                    data = uri.getQueryParameter("data").split(split);
+                    Log.d(TAG, "data.length :" + data.length);
+                    return getPdus(itemCount, dataCount, data);
+                } else {
+                    Log.e(TAG, "MMS get pdu date return null");
+                    return null;
+                }
             default:
                 Log.e(TAG, "query: invalid request: " + uri);
                 return null;
@@ -304,6 +386,122 @@
         }
     }
 
+    private byte[] getPduDataFromFile(String pduPath, String authority) {
+        FileInputStream fileInputStream = null;
+        byte[] data = null;
+        try {
+            Uri fileUri = FileProvider.getUriForFile(getContext(), authority, new File(pduPath));
+            ParcelFileDescriptor parcelFileDescriptor =
+                    getContext().getContentResolver().openFileDescriptor(fileUri, "r");
+            fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
+            data = new byte[fileInputStream.available()];
+            fileInputStream.read(data);
+        } catch (Exception e) {
+            Log.e(TAG, "read file exception :", e);
+        } finally {
+            try {
+                if (fileInputStream != null) {
+                    fileInputStream.close();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "close file stream exception :", e);
+            }
+        }
+        return data;
+    }
+
+    private Uri restorePduFile(Uri uri, String pduPath, String authority) {
+        Uri msgUri = null;
+        if (uri == null || TextUtils.isEmpty(pduPath) || TextUtils.isEmpty(authority)) {
+            return null;
+        }
+
+        try {
+            byte[] pduData = getPduDataFromFile(pduPath, authority);
+            PduPersister pduPersister = PduPersister.getPduPersister(getContext());
+            if (pduData != null && pduData.length > 0) {
+                if (Mms.Sent.CONTENT_URI.equals(uri)
+                        || Mms.Inbox.CONTENT_URI.equals(uri)) {
+                    GenericPdu pdu = new PduParser(pduData, true).parse();
+                    msgUri = pduPersister.persist(
+                            pdu, uri, true, false, null);
+                } else {
+                    Log.e(TAG,"Unsupported uri :" + uri);
+                }
+            }
+        } catch (MmsException e) {
+            Log.e(TAG, "MmsException: ", e);
+        }
+        return msgUri;
+    }
+
+    private String getPduPath(String dir, ContentValues values) {
+        if (dir != null && values != null && values.containsKey(COLUMN_PDU_PATH)) {
+            String path = values.getAsString(COLUMN_PDU_PATH);
+            if (!TextUtils.isEmpty(path)) {
+                return dir + "/" + path;
+            }
+        }
+        return null;
+    }
+
+    private int restoreMms(Uri uri, ContentValues values, String dir, String authority) {
+        int count = 0;
+        Uri msgUri = restorePduFile(uri, getPduPath(dir, values), authority);
+        if (msgUri != null) {
+            String selection = Mms._ID + "=" + msgUri.getLastPathSegment();
+            values.remove(COLUMN_PDU_PATH);
+            ContentValues finalValues = new ContentValues(values);
+            // now only support bulkInsert pdu table.
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            count = db.update(TABLE_PDU, finalValues, selection, null);
+        }
+        return count;
+    }
+
+    @Override
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        String dir = uri.getQueryParameter("restore_dir");
+        String authority = uri.getQueryParameter("authorities");
+        if (TextUtils.isEmpty(dir)) {
+            return super.bulkInsert(uri, values);
+        }
+
+        Uri insertUri = null;
+        int match = sURLMatcher.match(uri);
+        switch (match) {
+            case MMS_INBOX:
+                insertUri = Mms.Inbox.CONTENT_URI;
+                break;
+            case MMS_SENT:
+                insertUri = Mms.Sent.CONTENT_URI;
+                break;
+            default:
+                return 0;
+        }
+
+        long token = Binder.clearCallingIdentity();
+        int count = 0;
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            for (ContentValues value : values) {
+                count += restoreMms(insertUri, value, dir, authority);
+            }
+
+            Log.d(TAG, "bulkInsert  request count: " + values.length
+                    + " successfully count : " + count);
+            if (count == values.length) {
+                db.setTransactionSuccessful();
+            }
+            return count;
+        } finally {
+            db.endTransaction();
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         final int callerUid = Binder.getCallingUid();
@@ -603,6 +801,15 @@
         return res;
     }
 
+    private String getFileName(String fileLocation) {
+        File f = new File(fileLocation);
+        String fileName = f.getName();
+        if (fileName.length() >= MAX_FILE_NAME_LENGTH) {
+            fileName = fileName.substring(0, MAX_FILE_NAME_LENGTH);
+        }
+        return fileName;
+    }
+
     private int getMessageBoxByMatch(int match) {
         switch (match) {
             case MMS_INBOX_ID:
@@ -687,6 +894,8 @@
                                          selectionArgs, uri);
         } else if (TABLE_PART.equals(table)) {
             deletedRows = deleteParts(db, finalSelection, selectionArgs);
+            cleanUpWords(db);
+            updateHasAttachment(db);
         } else if (TABLE_DRM.equals(table)) {
             deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
         } else {
@@ -701,12 +910,13 @@
 
     static int deleteMessages(Context context, SQLiteDatabase db,
             String selection, String[] selectionArgs, Uri uri) {
-        Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
+        Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID, Mms.THREAD_ID },
                 selection, selectionArgs, null, null, null);
         if (cursor == null) {
             return 0;
         }
 
+        HashSet<Long> threadIds = new HashSet<Long>();
         try {
             if (cursor.getCount() == 0) {
                 return 0;
@@ -715,12 +925,18 @@
             while (cursor.moveToNext()) {
                 deleteParts(db, Part.MSG_ID + " = ?",
                         new String[] { String.valueOf(cursor.getLong(0)) });
+                threadIds.add(cursor.getLong(1));
             }
+            cleanUpWords(db);
+            updateHasAttachment(db);
         } finally {
             cursor.close();
         }
 
         int count = db.delete(TABLE_PDU, selection, selectionArgs);
+        for (long thread : threadIds) {
+            MmsSmsDatabaseHelper.updateThread(db, thread);
+        }
         if (count > 0) {
             Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
             intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
@@ -732,6 +948,18 @@
         return count;
     }
 
+    private static void cleanUpWords(SQLiteDatabase db) {
+        db.execSQL("DELETE FROM words WHERE source_id not in (select _id from part) AND "
+                + "table_to_use = 2");
+    }
+
+    private static void updateHasAttachment(SQLiteDatabase db) {
+        db.execSQL("UPDATE threads SET has_attachment = CASE "
+                + "(SELECT COUNT(*) FROM part JOIN pdu WHERE part.mid = pdu._id AND "
+                + "pdu.thread_id = threads._id AND part.ct != 'text/plain' "
+                + "AND part.ct != 'application/smil') WHEN 0 THEN 0 ELSE 1 END");
+    }
+
     private static int deleteParts(SQLiteDatabase db, String selection,
             String[] selectionArgs) {
         return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
@@ -1038,6 +1266,7 @@
     private static final int MMS_DRM_STORAGE_ID           = 18;
     private static final int MMS_THREADS                  = 19;
     private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
+    private static final int MMS_GET_PDU                  = 21;
 
     private static final UriMatcher
             sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -1064,6 +1293,7 @@
         sURLMatcher.addURI("mms", "drm/#",      MMS_DRM_STORAGE_ID);
         sURLMatcher.addURI("mms", "threads",    MMS_THREADS);
         sURLMatcher.addURI("mms", "resetFilePerm/*",    MMS_PART_RESET_FILE_PERMISSION);
+        sURLMatcher.addURI("mms", "get-pdu",    MMS_GET_PDU);
     }
 
     private SQLiteOpenHelper mOpenHelper;
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
old mode 100644
new mode 100755
index 068f209..f2cbf49
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -106,19 +106,6 @@
                         "        AND " + Mms.MESSAGE_BOX + " != 3) " +
                         "  WHERE threads._id = new.thread_id; ";
 
-    private static final String UPDATE_THREAD_COUNT_ON_OLD =
-                        "  UPDATE threads SET message_count = " +
-                        "     (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
-                        "      ON threads._id = " + Sms.THREAD_ID +
-                        "      WHERE " + Sms.THREAD_ID + " = old.thread_id" +
-                        "        AND sms." + Sms.TYPE + " != 3) + " +
-                        "     (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
-                        "      ON threads._id = " + Mms.THREAD_ID +
-                        "      WHERE " + Mms.THREAD_ID + " = old.thread_id" +
-                        "        AND (m_type=132 OR m_type=130 OR m_type=128)" +
-                        "        AND " + Mms.MESSAGE_BOX + " != 3) " +
-                        "  WHERE threads._id = old.thread_id; ";
-
     private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
                         "BEGIN" +
                         "  UPDATE threads SET" +
@@ -168,21 +155,6 @@
                         PDU_UPDATE_THREAD_READ_BODY +
                         "END;";
 
-    private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE =
-                        "  UPDATE threads SET snippet = " +
-                        "   (SELECT snippet FROM" +
-                        "     (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
-                        "      UNION SELECT date, body AS snippet, thread_id FROM sms)" +
-                        "    WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
-                        "  WHERE threads._id = OLD.thread_id; " +
-                        "  UPDATE threads SET snippet_cs = " +
-                        "   (SELECT snippet_cs FROM" +
-                        "     (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
-                        "      UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
-                        "    WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
-                        "  WHERE threads._id = OLD.thread_id; ";
-
-
     // When a part is inserted, if it is not text/plain or application/smil
     // (which both can exist with text-only MMSes), then there is an attachment.
     // Set has_attachment=1 in the threads table for the thread in question.
@@ -208,28 +180,6 @@
                         "     WHERE part._id=new._id LIMIT 1); " +
                         " END";
 
-
-    // When a part is deleted (with the same non-text/SMIL constraint as when
-    // we set has_attachment), update the threads table for all threads.
-    // Unfortunately we cannot update only the thread that the part was
-    // attached to, as it is possible that the part has been orphaned and
-    // the message it was attached to is already gone.
-    private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER =
-                        "CREATE TRIGGER update_threads_on_delete_part " +
-                        " AFTER DELETE ON part " +
-                        " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " +
-                        " BEGIN " +
-                        "  UPDATE threads SET has_attachment = " +
-                        "   CASE " +
-                        "    (SELECT COUNT(*) FROM part JOIN pdu " +
-                        "     WHERE pdu.thread_id = threads._id " +
-                        "     AND part.ct != 'text/plain' AND part.ct != 'application/smil' " +
-                        "     AND part.mid = pdu._id)" +
-                        "   WHEN 0 THEN 0 " +
-                        "   ELSE 1 " +
-                        "   END; " +
-                        " END";
-
     // When the 'thread_id' column in the pdu table is updated, we need to run the trigger to update
     // the threads table's has_attachment column, if the message has an attachment in 'part' table
     private static final String PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER =
@@ -251,6 +201,9 @@
     private static boolean sTriedAutoIncrement = false;
     private static boolean sFakeLowStorageTest = false;     // for testing only
 
+    private static final String NO_SUCH_COLUMN_EXCEPTION_MESSAGE = "no such column";
+    private static final String NO_SUCH_TABLE_EXCEPTION_MESSAGE = "no such table";
+
     static final String DATABASE_NAME = "mmssms.db";
     static final int DATABASE_VERSION = 67;
     private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
@@ -502,6 +455,54 @@
         }
     }
 
+    private static void updateThreadDate(SQLiteDatabase db, long thread_id) {
+        if (thread_id <= 0) {
+            return;
+        }
+
+        try {
+            db.execSQL(
+                    "  UPDATE threads" +
+                            "  SET" +
+                            "  date =" +
+                            "    (SELECT date FROM" +
+                            "        (SELECT date * 1000 AS date, thread_id FROM pdu" +
+                            "         UNION SELECT date, thread_id FROM sms)" +
+                            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" +
+                            "  WHERE threads._id = " + thread_id + ";");
+        } catch (Throwable ex) {
+            Log.e(TAG, ex.getMessage(), ex);
+        }
+    }
+
+    public static void updateThreadsDate(SQLiteDatabase db, String where, String[] whereArgs) {
+        db.beginTransaction();
+        try {
+            if (where == null) {
+                where = "";
+            } else {
+                where = "WHERE (" + where + ")";
+            }
+            String query = "SELECT _id FROM threads " + where;
+            Cursor c = db.rawQuery(query, whereArgs);
+            if (c != null) {
+                try {
+                    Log.d(TAG, "updateThread count : " + c.getCount());
+                    while (c.moveToNext()) {
+                        updateThreadDate(db, c.getInt(0));
+                    }
+                } finally {
+                    c.close();
+                }
+            }
+            db.setTransactionSuccessful();
+        } catch (Throwable ex) {
+            Log.e(TAG, ex.getMessage(), ex);
+        } finally {
+            db.endTransaction();
+        }
+    }
+
     public static int deleteOneSms(SQLiteDatabase db, int message_id) {
         int thread_id = -1;
         // Find the thread ID that the specified SMS belongs to.
@@ -565,6 +566,39 @@
         PhoneFactory.localLog(TAG, logMsg);
     }
 
+    @Override
+    public void onOpen(SQLiteDatabase db) {
+        try {
+            // Try to access the table and create it if "no such table"
+            db.query(SmsProvider.TABLE_SMS, null, null, null, null, null, null);
+            checkAndUpdateSmsTable(db);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "onOpen: ex. ", e);
+            if (e.getMessage().startsWith(NO_SUCH_TABLE_EXCEPTION_MESSAGE)) {
+                createSmsTables(db);
+            }
+        }
+        try {
+            // Try to access the table and create it if "no such table"
+            db.query(MmsSmsProvider.TABLE_THREADS, null, null, null, null, null, null);
+            checkAndUpdateThreadsTable(db);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "onOpen: ex. ", e);
+            if (e.getMessage().startsWith(NO_SUCH_TABLE_EXCEPTION_MESSAGE)) {
+                createCommonTables(db);
+            }
+        }
+
+        // Improve the performance of deleting Mms.
+        dropMmsTriggers(db);
+    }
+
+    private void dropMmsTriggers(SQLiteDatabase db) {
+        db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part");
+        db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete");
+        db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
+    }
+
     private boolean isInitialCreateDone() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
         return sp.getBoolean(INITIAL_CREATE_DONE, false);
@@ -676,8 +710,18 @@
         createThreadIdDateIndex(db);
         createPartMidIndex(db);
         createAddrMsgIdIndex(db);
+        createPduPartIndex(db);
     }
 
+    private void createPduPartIndex(SQLiteDatabase db) {
+        try {
+            db.execSQL("CREATE INDEX IF NOT EXISTS index_part ON " + MmsProvider.TABLE_PART +
+                        " (mid);");
+        } catch (Exception ex) {
+            Log.e(TAG, "got exception creating indices: " + ex.toString());
+        }
+     }
+
     private void createThreadIdIndex(SQLiteDatabase db) {
         try {
             db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" +
@@ -835,9 +879,6 @@
         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_part");
         db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER);
 
-        db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part");
-        db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
-
         db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_pdu");
         db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER);
 
@@ -913,10 +954,6 @@
                 " SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " +
                 " END;");
 
-        db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete");
-        db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " +
-                " words WHERE source_id = OLD._id AND table_to_use = 2; END;");
-
         // Updates threads table whenever a message in pdu is updated.
         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
         db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
@@ -925,18 +962,6 @@
                    PDU_UPDATE_THREAD_CONSTRAINTS +
                    PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
 
-        // Update threads table whenever a message in pdu is deleted
-        db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
-        db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
-                   "AFTER DELETE ON pdu " +
-                   "BEGIN " +
-                   "  UPDATE threads SET " +
-                   "     date = (strftime('%s','now') * 1000)" +
-                   "  WHERE threads._id = old." + Mms.THREAD_ID + "; " +
-                   UPDATE_THREAD_COUNT_ON_OLD +
-                   UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE +
-                   "END;");
-
         // Updates threads table whenever a message is added to pdu.
         db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
         db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " +
@@ -1122,7 +1147,9 @@
                    Threads.ARCHIVED + " INTEGER DEFAULT 0," +
                    Threads.TYPE + " INTEGER DEFAULT 0," +
                    Threads.ERROR + " INTEGER DEFAULT 0," +
-                   Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
+                   Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," +
+                   Threads.ATTACHMENT_INFO + " TEXT," +
+                   Threads.NOTIFICATION + " INTEGER DEFAULT 0);");
 
         /**
          * This table stores the queue of messages to be sent/downloaded.
@@ -1713,7 +1740,6 @@
 
         // Add insert and delete triggers for keeping it up to date.
         db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER);
-        db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
     }
 
     private void upgradeDatabaseToVersion44(SQLiteDatabase db) {
@@ -1959,6 +1985,42 @@
                     + "display_originating_addr; " + e);
         }
     }
+    private void checkAndUpdateSmsTable(SQLiteDatabase db) {
+        try {
+            db.query(SmsProvider.TABLE_SMS, new String[] {"priority"}, null, null, null, null,
+                    null);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "checkAndUpgradeSmsTable: ex. ", e);
+            if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+                db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS + " ADD COLUMN "
+                        + "priority INTEGER DEFAULT -1");
+            }
+        }
+    }
+
+    private void checkAndUpdateThreadsTable(SQLiteDatabase db) {
+        try {
+            db.query(MmsSmsProvider.TABLE_THREADS, new String[] {Threads.ATTACHMENT_INFO},
+                    null, null, null, null, null);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "checkAndUpdateThreadsTable: ex. ", e);
+            if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+                db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS + " ADD COLUMN "
+                        + Threads.ATTACHMENT_INFO + " TEXT");
+            }
+        }
+
+        try {
+            db.query(MmsSmsProvider.TABLE_THREADS, new String[] {Threads.NOTIFICATION},
+                    null, null, null, null, null);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "checkAndUpdateThreadsTable: ex. ", e);
+            if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+                db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS + " ADD COLUMN "
+                        + Threads.NOTIFICATION + " INTEGER DEFAULT 0");
+            }
+        }
+    }
 
     @Override
     public synchronized  SQLiteDatabase getReadableDatabase() {
@@ -2146,7 +2208,9 @@
                 Threads.READ + " INTEGER DEFAULT 1," +
                 Threads.TYPE + " INTEGER DEFAULT 0," +
                 Threads.ERROR + " INTEGER DEFAULT 0," +
-                Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
+                Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," +
+                Threads.ATTACHMENT_INFO + " TEXT," +
+                Threads.NOTIFICATION + " INTEGER DEFAULT 0);");
 
         db.execSQL("INSERT INTO threads_temp SELECT * from threads;");
         db.execSQL("DROP TABLE threads;");
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
old mode 100644
new mode 100755
index f5aeadc..d5253c2
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -100,7 +100,16 @@
     private static final int URI_FIRST_LOCKED_MESSAGE_ALL          = 16;
     private static final int URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID = 17;
     private static final int URI_MESSAGE_ID_TO_THREAD              = 18;
+    private static final int URI_MESSAGES_COUNT                    = 19;
+    private static final int URI_UPDATE_THREAD_DATE                = 20;
+    private static final int URI_SEARCH_MESSAGE                    = 21;
+    private static final int URI_ADDRESS_TO_THREAD                 = 22;
+    // Escape character
+    private static final char SEARCH_ESCAPE_CHARACTER = '!';
 
+    public static final int SEARCH_MODE_CONTENT = 0;
+    public static final int SEARCH_MODE_NAME = 1;
+    private static final long RESULT_FOR_ID_NOT_FOUND = -1L;
     /**
      * the name of the table that is used to store the queue of
      * messages(both MMS and SMS) to be sent/downloaded.
@@ -111,6 +120,7 @@
      * the name of the table that is used to store the canonical addresses for both SMS and MMS.
      */
     static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
+    private static final String DEFAULT_STRING_ZERO = "0";
 
     /**
      * the name of the table that is used to store the conversation threads.
@@ -253,9 +263,14 @@
                 AUTHORITY, "conversations/#/subject",
                 URI_CONVERSATIONS_SUBJECT);
 
+        // URI for obtaining all short message count
+        URI_MATCHER.addURI(AUTHORITY, "messagescount", URI_MESSAGES_COUNT);
+
         // URI for deleting obsolete threads.
         URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS);
 
+        URI_MATCHER.addURI(AUTHORITY, "search-message", URI_SEARCH_MESSAGE);
+
         URI_MATCHER.addURI(
                 AUTHORITY, "messages/byphone/*",
                 URI_MESSAGES_BY_PHONE);
@@ -265,6 +280,8 @@
         // may be present.
         URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID);
 
+        URI_MATCHER.addURI(AUTHORITY, "update-date", URI_UPDATE_THREAD_DATE);
+
         // Use this pattern to query the canonical address by given ID.
         URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS);
 
@@ -299,6 +316,7 @@
         URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID);
 
         URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD);
+        URI_MATCHER.addURI(AUTHORITY, "address", URI_ADDRESS_TO_THREAD);
         initializeColumnSets();
     }
 
@@ -373,6 +391,8 @@
                 cursor = getConversationMessages(uri.getPathSegments().get(1), projection,
                         selection, sortOrder, smsTable, pduTable);
                 break;
+            case URI_MESSAGES_COUNT:
+                return getAllMessagesCount();
             case URI_CONVERSATIONS_RECIPIENTS:
                 cursor = getConversationById(
                         uri.getPathSegments().get(1), projection, selection,
@@ -480,6 +500,9 @@
                 }
                 break;
             }
+            case URI_SEARCH_MESSAGE:
+                cursor = getSearchMessages(uri, db, smsTable, pduTable);
+                break;
             case URI_PENDING_MSG: {
                 String protoName = uri.getQueryParameter("protocol");
                 String msgId = uri.getQueryParameter("message");
@@ -526,6 +549,15 @@
                         projection, selection, sortOrder, smsTable, pduTable);
                 break;
             }
+
+            case URI_ADDRESS_TO_THREAD: {
+                String address = uri.getQueryParameter("address_info");
+                if (DEBUG) {
+                    Log.d(LOG_TAG,"query threadId by address:"+address);
+                }
+                cursor = getThreadIdsByRecipient(getRecipientIdByAddress(address));
+                break;
+            }
             default:
                 throw new IllegalStateException("Unrecognized URI:" + uri);
         }
@@ -536,6 +568,66 @@
         return cursor;
     }
 
+    private long getRecipientIdByAddress(String address) {
+        boolean isEmail = Mms.isEmailAddress(address);
+        String refinedAddress = isEmail ? address.toLowerCase() : address;
+        String selection = "address=?";
+        String[] selectionArgs;
+        if (isEmail) {
+            selectionArgs = new String[] { refinedAddress };
+        } else {
+            selection += " OR "
+                    + String.format("PHONE_NUMBERS_EQUAL(address, ?, %d)",
+                            (mUseStrictPhoneNumberComparation ? 1 : 0));
+            selectionArgs = new String[] { refinedAddress, refinedAddress };
+        }
+        Log.d(LOG_TAG, "getRecipientIdByAddress selection:" + selection);
+        Cursor cursor = null;
+
+        try {
+            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+            cursor = db.query("canonical_addresses",
+                    ID_PROJECTION,
+                    selection, selectionArgs, null, null, null);
+
+            if (cursor.getCount() == 0) {
+                return RESULT_FOR_ID_NOT_FOUND;
+            }
+
+            if (cursor.moveToFirst()) {
+                return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return RESULT_FOR_ID_NOT_FOUND;
+    }
+
+    private Cursor getThreadIdsByRecipient(long recipientId) {
+        if (recipientId == RESULT_FOR_ID_NOT_FOUND)
+            return null;
+        String recipientIds = String.valueOf(recipientId);
+        String THREAD_QUERY = "SELECT _id FROM threads "
+            + "WHERE recipient_ids = ? or recipient_ids like '"
+            + recipientIds + " %' or recipient_ids like '% "
+            + recipientIds + "' or recipient_ids like '% "
+            + recipientIds + " %'";
+
+        if (DEBUG) {
+            Log.v(LOG_TAG, "getThreadId THREAD_QUERY: " + THREAD_QUERY
+                    + ", recipientIds=" + recipientIds);
+        }
+        try {
+            return mOpenHelper.getReadableDatabase().rawQuery(THREAD_QUERY,
+                    new String[] { recipientIds });
+        } catch (Exception e) {
+            Log.d(LOG_TAG, "get thread by recipient id exception:" + e);
+            return null;
+        }
+    }
+
     /**
      * Return the canonical address ID for this address.
      */
@@ -603,7 +695,7 @@
         for (String address : addresses) {
             if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
                 long id = getSingleAddressId(address);
-                if (id != -1L) {
+                if (id != RESULT_FOR_ID_NOT_FOUND) {
                     result.add(id);
                 } else {
                     Log.e(LOG_TAG, "getAddressIds: address ID not found for " + address);
@@ -1002,6 +1094,17 @@
     }
 
     /**
+     * Return the SMS messages count on phone
+     */
+    private Cursor getAllMessagesCount() {
+        String unionQuery = "select sum(a) AS count, 1 AS _id "
+                + "from (" + "select count(sms._id) as a, 2 AS b from sms, threads"
+                + " where thread_id NOTNULL AND thread_id = threads._id)";
+
+        return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
+    }
+
+    /**
      * Return the union of MMS and SMS messages whose recipients
      * included this phone number.
      *
@@ -1310,8 +1413,14 @@
         switch(URI_MATCHER.match(uri)) {
             case URI_CONVERSATIONS_MESSAGES:
                 String threadIdString = uri.getPathSegments().get(1);
-                affectedRows = updateConversation(threadIdString, values,
-                        selection, selectionArgs, callerUid, callerPkg);
+                if (values.containsKey(Threads.NOTIFICATION) ||
+                        values.containsKey(Threads.ATTACHMENT_INFO)) {
+                    String finalSelection = concatSelections(selection, "_id=" + threadIdString);
+                    affectedRows = db.update(TABLE_THREADS, values, finalSelection, null);
+                } else {
+                    affectedRows = updateConversation(threadIdString, values,
+                            selection, selectionArgs, callerUid, callerPkg);
+                }
                 break;
 
             case URI_PENDING_MSG:
@@ -1337,6 +1446,9 @@
                 break;
             }
 
+            case URI_UPDATE_THREAD_DATE:
+                MmsSmsDatabaseHelper.updateThreadsDate(db, selection, selectionArgs);
+                break;
             default:
                 throw new UnsupportedOperationException(
                         NO_DELETES_INSERTS_OR_UPDATES + uri);
@@ -1414,6 +1526,132 @@
         writer.println("Default SMS app: " + defaultSmsApp);
     }
 
+    private Cursor getSearchMessages(Uri uri, SQLiteDatabase db,
+                                     String smsTable, String pduTable) {
+        String searchString = "%" + uri.getQueryParameter("key_str") + "%";
+        String threadIdString = uri.getQueryParameter("thread_ids");
+        int searchMode = SEARCH_MODE_NAME;
+        if (threadIdString == null || threadIdString.equals(DEFAULT_STRING_ZERO))
+            searchMode = SEARCH_MODE_CONTENT;
+        if (DEBUG) {
+            Log.d(LOG_TAG, "keystr=" + searchString +
+                    "|searchMode=" + searchMode + "|threadIdString=" + threadIdString);
+        }
+        String rawQuery = getConversationQueryString(searchMode, smsTable,
+                pduTable, threadIdString);
+        String[] strArray = new String[]{searchString, searchString, searchString,
+                searchString, searchString};
+        return db.rawQuery(rawQuery, strArray);
+    }
+
+    private String getConversationQueryString(int searchMode,
+                String smsTable, String pduTable, final String threadIds) {
+        String nameQuery = "";
+        String smsContentQuery = "";
+        String pduContentQuery = "";
+        String rawQuery = "";
+
+        final String NAME_PROJECTION = "threads._id AS _id,"
+                + "threads.date AS date,"
+                + "threads.message_count AS message_count,"
+                + "threads.recipient_ids AS recipient_ids,"
+                + "threads.snippet AS snippet,"
+                + "threads.snippet_cs AS snippet_cs,"
+                + "threads.read AS read,"
+                + "NULL AS error,"
+                + "threads.has_attachment AS has_attachment,"
+                + "threads.attachment_info AS attachment_info";
+        final String SMS_PROJECTION = "threads._id AS _id,"
+                + smsTable + ".date AS date,"
+                + "threads.message_count AS message_count,"
+                + "threads.recipient_ids AS recipient_ids,"
+                + smsTable + ".body AS snippet,"
+                + "threads.snippet_cs AS snippet_cs,"
+                + "threads.read AS read,"
+                + "NULL AS error,"
+                + "threads.has_attachment AS has_attachment,"
+                + "'SMS' AS attachment_info";
+        final String PDU_PROJECTION = "threads._id AS _id,"
+                + pduTable + ".date * 1000 AS date,"
+                + "threads.message_count AS message_count,"
+                + "threads.recipient_ids AS recipient_ids,"
+                + pduTable + ".sub AS snippet,"
+                + pduTable + ".sub_cs AS snippet_cs,"
+                + "threads.read AS read,"
+                + "NULL AS error,"
+                + "threads.has_attachment AS has_attachment,"
+                + "part.text AS attachment_info";
+
+        if (searchMode == SEARCH_MODE_NAME) {
+            nameQuery = String.format(
+                    "SELECT %s FROM threads WHERE threads._id in (%s)",
+                    NAME_PROJECTION,
+                    threadIds);
+            smsContentQuery = String.format("SELECT %s FROM threads, " + smsTable
+                    + " WHERE ("
+                    + "(threads._id NOT in (%s))"
+                    + " AND (" + smsTable + ".thread_id = " + "threads._id )"
+                    + " AND ((" + smsTable + ".body LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (" + smsTable + ".address LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')))"
+                    + " GROUP BY threads._id",
+                    SMS_PROJECTION,
+                    threadIds);
+            pduContentQuery = String.format("SELECT %s FROM threads, addr, part, " + pduTable
+                    + " WHERE((threads._id NOT in (%s))"
+                    + " AND (addr.msg_id=" + pduTable + "._id)"
+                    + " AND (addr.type=%d)"
+                    + " AND (part.mid=" + pduTable + "._id)"
+                    + " AND (part.ct='text/plain')"
+                    + " AND (threads._id =" + pduTable + ".thread_id)"
+                    + " AND ((part.text LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (addr.address LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (" + pduTable + ".sub LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')))",
+                    PDU_PROJECTION,
+                    threadIds,
+                    PduHeaders.TO);
+
+            rawQuery = String.format(
+                    "%s UNION %s UNION %s GROUP BY threads._id ORDER BY date DESC",
+                    nameQuery,
+                    smsContentQuery,
+                    pduContentQuery);
+        } else {
+            smsContentQuery = String.format("SELECT %s FROM threads, " + smsTable
+                    + " WHERE ("
+                    + "(" + smsTable + ".thread_id = " + "threads._id )"
+                    + " AND ((" + smsTable + ".body LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (" + smsTable + ".address LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')))"
+                    + " GROUP BY threads._id",
+                    SMS_PROJECTION);
+            pduContentQuery = String.format("SELECT %s FROM threads, addr, part, " + pduTable
+                    + " WHERE((addr.msg_id=" + pduTable + "._id)"
+                    + " AND (addr.type=%d)"
+                    + " AND (part.mid=" + pduTable + "._id)"
+                    + " AND (part.ct='text/plain')"
+                    + " AND (threads._id =" + pduTable + ".thread_id)"
+                    + " AND ((part.text LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (addr.address LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+                    + " OR (" + pduTable + ".sub LIKE ? ESCAPE '"
+                    + SEARCH_ESCAPE_CHARACTER + "')))",
+                    PDU_PROJECTION,
+                    PduHeaders.TO);
+
+            rawQuery = String.format(
+                    "%s UNION %s GROUP BY threads._id ORDER BY date DESC",
+                    smsContentQuery,
+                    pduContentQuery);
+        }
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getConversationQueryString = " + rawQuery);
+        }
+        return rawQuery;
+    }
+
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         if (METHOD_IS_RESTORING.equals(method)) {
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
old mode 100644
new mode 100755
index 81f732b..2ff78ab
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -16,6 +16,12 @@
 
 package com.android.providers.telephony;
 
+import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.ENCODING_7BIT;
+import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
+import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
+import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
+
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.ContentProvider;
@@ -37,16 +43,32 @@
 import android.provider.Telephony.MmsSms;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
 
 public class SmsProvider extends ContentProvider {
     /* No response constant from SmsResponse */
@@ -61,7 +83,16 @@
     private static final String TABLE_WORDS = "words";
     static final String VIEW_SMS_RESTRICTED = "sms_restricted";
 
+    private static final int DELETE_SUCCESS = 1;
+    private static final int DELETE_FAIL = 0;
+    private static final int MESSAGE_ID = 1;
+    private static final int SLOT1 = 0;
+    private static final int SLOT2 = 1;
     private static final Integer ONE = Integer.valueOf(1);
+    private static final int OFFSET_ADDRESS_LENGTH = 0;
+    private static final int OFFSET_TOA = 1;
+    private static final int OFFSET_ADDRESS_VALUE = 2;
+    private static final int TIMESTAMP_LENGTH = 7;  // See TS 23.040 9.2.3.11
 
     private static final String[] CONTACT_QUERY_PROJECTION =
             new String[] { Contacts.Phones.PERSON_ID };
@@ -70,6 +101,10 @@
     /** Delete any raw messages or message segments marked deleted that are older than an hour. */
     static final long RAW_MESSAGE_EXPIRE_AGE_MS = (long) (60 * 60 * 1000);
 
+    private static final String SMS_BOX_ID = "box_id";
+    private static final String INSERT_SMS_INTO_ICC_SUCCESS = "success";
+    private static final String INSERT_SMS_INTO_ICC_FAIL = "fail";
+
     /**
      * These are the columns that are available when reading SMS
      * messages from the ICC.  Columns whose names begin with "is_"
@@ -90,7 +125,8 @@
         "type",                         // depend on getStatusOnIcc
         "locked",                       // Always 0 (false).
         "error_code",                   // Always -1 (NO_ERROR_CODE), previously it was 0 always.
-        "_id"
+        "_id",
+        "sub_id"
     };
 
     @Override
@@ -299,6 +335,22 @@
                     return ret;
                 }
 
+            case SMS_ALL_ICC1:
+                return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT1)[0]);
+
+            case SMS_ICC1:
+                String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+                return getSingleMessageFromIcc(SubscriptionManager.getSubId(SLOT1)[0],
+                    Integer.parseInt(messageIndexIcc1));
+
+            case SMS_ALL_ICC2:
+                return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT2)[0]);
+
+            case SMS_ICC2:
+                String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+                return getSingleMessageFromIcc(SubscriptionManager.getSubId(SLOT2)[0],
+                    Integer.parseInt(messageIndexIcc2));
+
             default:
                 Log.e(TAG, "Invalid request: " + url);
                 return null;
@@ -338,7 +390,7 @@
         return mCeOpenHelper;
     }
 
-    private Object[] convertIccToSms(SmsMessage message, int id) {
+    private Object[] convertIccToSms(SmsMessage message, int id, int subId) {
         int statusOnIcc = message.getStatusOnIcc();
         int type = Sms.MESSAGE_TYPE_ALL;
         switch (statusOnIcc) {
@@ -353,9 +405,7 @@
                 type = Sms.MESSAGE_TYPE_OUTBOX;
                 break;
         }
-        // N.B.: These calls must appear in the same order as the
-        // columns appear in ICC_COLUMNS.
-        Object[] row = new Object[13];
+        Object[] row = new Object[14];
         row[0] = message.getServiceCenterAddress();
         row[1] =
                 (type == Sms.MESSAGE_TYPE_INBOX)
@@ -373,6 +423,7 @@
         row[10] = 0;      // locked
         row[11] = NO_ERROR_CODE;
         row[12] = id;
+        row[13] = subId;
         return row;
     }
 
@@ -393,7 +444,7 @@
         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
         long token = Binder.clearCallingIdentity();
         try {
-            messages = smsManager.getMessagesFromIcc();
+            messages = SmsManager.getSmsManagerForSubscriptionId(subId).getMessagesFromIcc();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -404,7 +455,7 @@
                     "No message in index " + messageIndex + " for subId " + subId);
         }
         MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1);
-        cursor.addRow(convertIccToSms(message, 0));
+        cursor.addRow(convertIccToSms(message, 0, subId));
         return cursor;
     }
 
@@ -424,7 +475,8 @@
         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call
         long token = Binder.clearCallingIdentity();
         try {
-            messages = smsManager.getMessagesFromIcc();
+            messages = SmsManager.getSmsManagerForSubscriptionId(subId)
+                    .getMessagesFromIcc();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -434,7 +486,7 @@
         for (int i = 0; i < count; i++) {
             SmsMessage message = messages.get(i);
             if (message != null) {
-                cursor.addRow(convertIccToSms(message, i));
+                cursor.addRow(convertIccToSms(message, i, subId));
             }
         }
         return cursor;
@@ -588,6 +640,8 @@
                 break;
 
             case SMS_ALL_ICC:
+                return insertMessageIntoIcc(url, initialValues);
+
             case SMS_ALL_ICC_SUBID:
                 int subId;
                 if (match == SMS_ALL_ICC) {
@@ -761,6 +815,339 @@
         return null;
     }
 
+    private Uri insertMessageIntoIcc(Uri uri, ContentValues values) {
+        if (values == null) {
+            return Uri.withAppendedPath(uri, INSERT_SMS_INTO_ICC_FAIL);
+        }
+        int subId = values.getAsInteger(PhoneConstants.SUBSCRIPTION_KEY);
+        String address = values.getAsString(Sms.ADDRESS);
+        String message = values.getAsString(Sms.BODY);
+        int boxId = values.getAsInteger(SMS_BOX_ID);
+        long timestamp = values.getAsLong(Sms.DATE);
+        byte pdu[] = null;
+        int status;
+        if (Sms.isOutgoingFolder(boxId)) {
+            pdu = SmsMessage.getSubmitPdu(null, address, message, false, subId).encodedMessage;
+            status = SmsManager.STATUS_ON_ICC_SENT;
+        } else {
+            pdu = getDeliveryPdu(null, address, message, timestamp, subId);
+            status = SmsManager.STATUS_ON_ICC_READ;
+        }
+        boolean result = SmsManager.getSmsManagerForSubscriptionId(subId).copyMessageToIcc(null,
+                pdu, status);
+        return Uri.withAppendedPath(uri,
+                (result ? INSERT_SMS_INTO_ICC_SUCCESS : INSERT_SMS_INTO_ICC_FAIL));
+    }
+
+    /**
+     * Generate a Delivery PDU byte array. see getSubmitPdu for reference.
+     */
+    public static byte[] getDeliveryPdu(String scAddress, String destinationAddress, String message,
+            long date, int subscription) {
+        if (isCdmaPhone(subscription)) {
+            return getCdmaDeliveryPdu(scAddress, destinationAddress, message, date);
+        } else {
+            return getGsmDeliveryPdu(scAddress, destinationAddress, message, date, null,
+                    ENCODING_UNKNOWN);
+        }
+    }
+
+    private static boolean isCdmaPhone(int subscription) {
+        boolean isCdma = false;
+        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subscription);
+        if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) {
+            isCdma = true;
+        }
+        return isCdma;
+    }
+
+    public static byte[] getCdmaDeliveryPdu(String scAddress, String destinationAddress,
+            String message, long date) {
+        // Perform null parameter checks.
+        if (message == null || destinationAddress == null) {
+            Log.d(TAG, "getCDMADeliveryPdu,message =null");
+            return null;
+        }
+
+        // according to submit pdu encoding as written in privateGetSubmitPdu
+
+        // MTI = SMS-DELIVERY, UDHI = header != null
+        byte[] header = null;
+        byte mtiByte = (byte) (0x00 | (header != null ? 0x40 : 0x00));
+        ByteArrayOutputStream headerStream = getDeliveryPduHeader(destinationAddress, mtiByte);
+
+        ByteArrayOutputStream byteStream = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40);
+
+        DataOutputStream dos = new DataOutputStream(byteStream);
+        // int status,Status of message. See TS 27.005 3.1, "<stat>"
+
+        /* 0 = "REC UNREAD" */
+        /* 1 = "REC READ" */
+        /* 2 = "STO UNSENT" */
+        /* 3 = "STO SENT" */
+
+        try {
+            // int uTeleserviceID;
+            int uTeleserviceID = 0; //.TELESERVICE_CT_WAP;// int
+            dos.writeInt(uTeleserviceID);
+
+            // unsigned char bIsServicePresent
+            byte bIsServicePresent = 0;// byte
+            dos.writeInt(bIsServicePresent);
+
+            // uServicecategory
+            int uServicecategory = 0;// int
+            dos.writeInt(uServicecategory);
+
+            // RIL_CDMA_SMS_Address
+            // digit_mode
+            // number_mode
+            // number_type
+            // number_plan
+            // number_of_digits
+            // digits[]
+            CdmaSmsAddress destAddr = CdmaSmsAddress.parse(PhoneNumberUtils
+                    .cdmaCheckAndProcessPlusCode(destinationAddress));
+            if (destAddr == null)
+                return null;
+            dos.writeByte(destAddr.digitMode);// int
+            dos.writeByte(destAddr.numberMode);// int
+            dos.writeByte(destAddr.ton);// int
+            Log.d(TAG, "message type=" + destAddr.ton + "destination add=" + destinationAddress
+                    + "message=" + message);
+            dos.writeByte(destAddr.numberPlan);// int
+            dos.writeByte(destAddr.numberOfDigits);// byte
+            dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+
+            // RIL_CDMA_SMS_Subaddress
+            // Subaddress is not supported.
+            dos.writeByte(0); // subaddressType int
+            dos.writeByte(0); // subaddr_odd byte
+            dos.writeByte(0); // subaddr_nbr_of_digits byte
+
+            SmsHeader smsHeader = new SmsHeader().fromByteArray(headerStream.toByteArray());
+            UserData uData = new UserData();
+            uData.payloadStr = message;
+            // uData.userDataHeader = smsHeader;
+            uData.msgEncodingSet = true;
+            uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+
+            BearerData bearerData = new BearerData();
+            bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+
+            bearerData.deliveryAckReq = false;
+            bearerData.userAckReq = false;
+            bearerData.readAckReq = false;
+            bearerData.reportReq = false;
+
+            bearerData.userData = uData;
+
+            byte[] encodedBearerData = BearerData.encode(bearerData);
+            if (null != encodedBearerData) {
+                // bearer data len
+                dos.writeByte(encodedBearerData.length);// int
+                Log.d(TAG, "encodedBearerData length=" + encodedBearerData.length);
+
+                // aBearerData
+                dos.write(encodedBearerData, 0, encodedBearerData.length);
+            } else {
+                dos.writeByte(0);
+            }
+
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing dos", e);
+        } finally {
+            try {
+                if (null != byteStream) {
+                    byteStream.close();
+                }
+
+                if (null != dos) {
+                    dos.close();
+                }
+
+                if (null != headerStream) {
+                    headerStream.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error close dos", e);
+            }
+        }
+
+        return byteStream.toByteArray();
+    }
+
+    /**
+     * Generate a Delivery PDU byte array. see getSubmitPdu for reference.
+     */
+    public static byte[] getGsmDeliveryPdu(String scAddress, String destinationAddress,
+            String message, long date, byte[] header, int encoding) {
+        // Perform null parameter checks.
+        if (message == null || destinationAddress == null) {
+            return null;
+        }
+
+        // MTI = SMS-DELIVERY, UDHI = header != null
+        byte mtiByte = (byte)(0x00 | (header != null ? 0x40 : 0x00));
+        ByteArrayOutputStream bo = getDeliveryPduHeader(destinationAddress, mtiByte);
+        // User Data (and length)
+        byte[] userData;
+        if (encoding == ENCODING_UNKNOWN) {
+            // First, try encoding it with the GSM alphabet
+            encoding = ENCODING_7BIT;
+        }
+        try {
+            if (encoding == ENCODING_7BIT) {
+                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 0, 0);
+            } else { //assume UCS-2
+                try {
+                    userData = encodeUCS2(message, header);
+                } catch (UnsupportedEncodingException uex) {
+                    Log.e("GSM", "Implausible UnsupportedEncodingException ",
+                            uex);
+                    return null;
+                }
+            }
+        } catch (EncodeException ex) {
+            // Encoding to the 7-bit alphabet failed. Let's see if we can
+            // encode it as a UCS-2 encoded message
+            try {
+                userData = encodeUCS2(message, header);
+                encoding = ENCODING_16BIT;
+            } catch (UnsupportedEncodingException uex) {
+                Log.e("GSM", "Implausible UnsupportedEncodingException ",
+                            uex);
+                return null;
+            }
+        }
+
+        if (encoding == ENCODING_7BIT) {
+            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+                // Message too long
+                return null;
+            }
+            bo.write(0x00);
+        } else { //assume UCS-2
+            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+                // Message too long
+                return null;
+            }
+            // TP-Data-Coding-Scheme
+            // Class 3, UCS-2 encoding, uncompressed
+            bo.write(0x0b);
+        }
+        byte[] timestamp = getTimestamp(date);
+        bo.write(timestamp, 0, timestamp.length);
+
+        bo.write(userData, 0, userData.length);
+        return bo.toByteArray();
+    }
+
+    private static ByteArrayOutputStream getDeliveryPduHeader(
+            String destinationAddress, byte mtiByte) {
+        ByteArrayOutputStream bo = new ByteArrayOutputStream(
+                MAX_USER_DATA_BYTES + 40);
+        bo.write(mtiByte);
+
+        byte[] daBytes;
+        daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+        if (daBytes == null) {
+            Log.d(TAG, "The number can not convert to BCD, it's an An alphanumeric address, " +
+                    "destinationAddress = " + destinationAddress);
+            // Convert address to GSM 7 bit packed bytes.
+            try {
+                byte[] numberdata = GsmAlphabet
+                        .stringToGsm7BitPacked(destinationAddress);
+                // Get the real address data
+                byte[] addressData = new byte[numberdata.length - 1];
+                System.arraycopy(numberdata, 1, addressData, 0, addressData.length);
+
+                daBytes = new byte[addressData.length + OFFSET_ADDRESS_VALUE];
+                // Get the address length
+                int addressLen = numberdata[0];
+                daBytes[OFFSET_ADDRESS_LENGTH] = (byte) ((addressLen * 7 % 4 != 0 ?
+                        addressLen * 7 / 4 + 1 : addressLen * 7 / 4));
+                // Set address type to Alphanumeric according to 3GPP TS 23.040 [9.1.2.5]
+                daBytes[OFFSET_TOA] = (byte) 0xd0;
+                System.arraycopy(addressData, 0, daBytes, OFFSET_ADDRESS_VALUE, addressData.length);
+            } catch (Exception e) {
+                Log.e(TAG, "Exception when encoding to 7 bit data.");
+            }
+        } else {
+            // destination address length in BCD digits, ignoring TON byte and pad
+            // TODO Should be better.
+            bo.write((daBytes.length - 1) * 2
+                    - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+        }
+
+        // destination address
+        bo.write(daBytes, 0, daBytes.length);
+
+        // TP-Protocol-Identifier
+        bo.write(0);
+        return bo;
+    }
+
+    private static byte[] encodeUCS2(String message, byte[] header)
+        throws UnsupportedEncodingException {
+        byte[] userData, textPart;
+        textPart = message.getBytes("utf-16be");
+
+        if (header != null) {
+            // Need 1 byte for UDHL
+            userData = new byte[header.length + textPart.length + 1];
+
+            userData[0] = (byte)header.length;
+            System.arraycopy(header, 0, userData, 1, header.length);
+            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+        }
+        else {
+            userData = textPart;
+        }
+        byte[] ret = new byte[userData.length+1];
+        ret[0] = (byte) (userData.length & 0xff );
+        System.arraycopy(userData, 0, ret, 1, userData.length);
+        return ret;
+    }
+
+    private static byte[] getTimestamp(long time) {
+        // See TS 23.040 9.2.3.11
+        byte[] timestamp = new byte[TIMESTAMP_LENGTH];
+        SimpleDateFormat sdf = new SimpleDateFormat("yyMMddkkmmss:Z", Locale.US);
+        String[] date = sdf.format(time).split(":");
+        // generate timezone value
+        String timezone = date[date.length - 1];
+        String signMark = timezone.substring(0, 1);
+        int hour = Integer.parseInt(timezone.substring(1, 3));
+        int min = Integer.parseInt(timezone.substring(3));
+        int timezoneValue = hour * 4 + min / 15;
+        // append timezone value to date[0] (time string)
+        String timestampStr = date[0] + timezoneValue;
+
+        int digitCount = 0;
+        for (int i = 0; i < timestampStr.length(); i++) {
+            char c = timestampStr.charAt(i);
+            int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
+            timestamp[(digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+            digitCount++;
+        }
+
+        if (signMark.equals("-")) {
+            timestamp[timestamp.length - 1] = (byte) (timestamp[timestamp.length - 1] | 0x08);
+        }
+
+        return timestamp;
+    }
+
+    private static int charToBCD(char c) {
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        } else {
+            throw new RuntimeException ("invalid char for BCD " + c);
+        }
+    }
+
     private boolean isSupportedType(int messageType) {
         return (messageType == Sms.MESSAGE_TYPE_INBOX)
                 || (messageType == Sms.MESSAGE_TYPE_OUTBOX)
@@ -908,6 +1295,16 @@
                         UserHandle.USER_ALL);
                 return success ? 1 : 0; // return deleted count
 
+            case SMS_ICC1:
+                String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+                return deleteMessageFromIcc(SubscriptionManager.getSubId(SLOT1)[0],
+                    Integer.parseInt(messageIndexIcc1)) ? 1 : 0;
+
+            case SMS_ICC2:
+                String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+                return deleteMessageFromIcc(SubscriptionManager.getSubId(SLOT2)[0],
+                    Integer.parseInt(messageIndexIcc2)) ? 1 : 0;
+
             default:
                 throw new IllegalArgumentException("Unknown URL");
         }
@@ -1082,6 +1479,11 @@
     private static final int SMS_ALL_ICC_SUBID = 29;
     private static final int SMS_ICC_SUBID = 30;
 
+    private static final int SMS_ALL_ICC1 = 31;
+    private static final int SMS_ICC1 = 32;
+    private static final int SMS_ALL_ICC2 = 33;
+    private static final int SMS_ICC2 = 34;
+
     private static final UriMatcher sURLMatcher =
             new UriMatcher(UriMatcher.NO_MATCH);
 
@@ -1114,6 +1516,10 @@
         sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
         sURLMatcher.addURI("sms", "icc_subId/#", SMS_ALL_ICC_SUBID);
         sURLMatcher.addURI("sms", "icc_subId/#/#", SMS_ICC_SUBID);
+        sURLMatcher.addURI("sms", "icc1", SMS_ALL_ICC1);
+        sURLMatcher.addURI("sms", "icc1/#", SMS_ICC1);
+        sURLMatcher.addURI("sms", "icc2", SMS_ALL_ICC2);
+        sURLMatcher.addURI("sms", "icc2/#", SMS_ICC2);
         //we keep these for not breaking old applications
         sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
         sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index 5c764c8..930a98a 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -631,12 +631,16 @@
         ContentValues[] values = new ContentValues[bulkInsertSize];
         while (jsonReader.hasNext()) {
             ContentValues cv = readSmsValuesFromReader(jsonReader);
-            if (doesSmsExist(cv)) {
-                continue;
-            }
-            values[(msgCount++) % bulkInsertSize] = cv;
-            if (msgCount % bulkInsertSize == 0) {
-                mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, values);
+            try {
+                if (mSmsProviderQuery.doesSmsExist(cv)) {
+                    continue;
+                }
+                values[(msgCount++) % bulkInsertSize] = cv;
+                if (msgCount % bulkInsertSize == 0) {
+                    mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, values);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "putSmsMessagesToProvider", e);
             }
         }
         if (msgCount % bulkInsertSize > 0) {
@@ -655,16 +659,20 @@
             if (DEBUG) {
                 Log.d(TAG, "putMmsMessagesToProvider " + mms);
             }
-            if (doesMmsExist(mms)) {
-                if (DEBUG) {
-                    Log.e(TAG, String.format("Mms: %s already exists", mms.toString()));
-                } else {
-                    Log.w(TAG, "Mms: Found duplicate MMS");
+            try {
+                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;
                 }
-                continue;
+                total++;
+                addMmsMessage(mms);
+            } catch (Exception e) {
+                Log.e(TAG, "putMmsMessagesToProvider", e);
             }
-            total++;
-            addMmsMessage(mms);
         }
         Log.d(TAG, "putMmsMessagesToProvider handled " + total + " new messages.");
     }
@@ -673,15 +681,34 @@
     static final String[] PROJECTION_ID = {BaseColumns._ID};
     private static final int ID_IDX = 0;
 
-    private boolean doesSmsExist(ContentValues smsValues) {
-        final String where = String.format(Locale.US, "%s = %d and %s = %s",
-                Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE),
-                Telephony.Sms.BODY,
-                DatabaseUtils.sqlEscapeString(smsValues.getAsString(Telephony.Sms.BODY)));
-        try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, PROJECTION_ID, where,
-                null, null)) {
-            return cursor != null && cursor.getCount() > 0;
+    /**
+     * Interface to allow mocking method for testing.
+     */
+    public interface SmsProviderQuery {
+        boolean doesSmsExist(ContentValues smsValues);
+    }
+
+    private SmsProviderQuery mSmsProviderQuery = new SmsProviderQuery() {
+        @Override
+        public boolean doesSmsExist(ContentValues smsValues) {
+            // The SMS body might contain '\0' characters (U+0000) such as in the case of
+            // http://b/160801497 . SQLite does not allow '\0' in String literals, but as of SQLite
+            // version 3.32.2 2020-06-04, it does allow them as selectionArgs; therefore, we're
+            // using the latter approach here.
+            final String selection = String.format(Locale.US, "%s=%d AND %s=?",
+                    Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE),
+                    Telephony.Sms.BODY);
+            String[] selectionArgs = new String[] { smsValues.getAsString(Telephony.Sms.BODY)};
+            try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, PROJECTION_ID,
+                    selection, selectionArgs, null)) {
+                return cursor != null && cursor.getCount() > 0;
+            }
         }
+    };
+
+    @VisibleForTesting
+    public void setSmsProviderQuery(SmsProviderQuery smsProviderQuery) {
+        mSmsProviderQuery = smsProviderQuery;
     }
 
     private boolean doesMmsExist(Mms mms) {
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 4966e5a..aca1edb 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -169,6 +169,9 @@
     private static final int URL_FILTERED = 18;
     private static final int URL_FILTERED_ID = 19;
     private static final int URL_ENFORCE_MANAGED = 20;
+    // URL_PREFERAPNSET and URL_PREFERAPNSET_USING_SUBID return all APNs for the current
+    // carrier which have an apn_set_id equal to the preferred APN
+    // (if no preferred APN, or preferred APN has no set id, the query will return null)
     private static final int URL_PREFERAPNSET = 21;
     private static final int URL_PREFERAPNSET_USING_SUBID = 22;
     private static final int URL_SIM_APN_LIST = 23;
@@ -2145,6 +2148,17 @@
                 }
                 // Update the network type bitmask to keep them sync.
                 networkTypeBitmask = convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
+                // Legacy bearer is deprecated, in order to be compatible with bearer_bitmask till
+                // both are removed (bearer_bitmask is marked as deprecated now), just appends
+                // bearer into bearer_bitmask only.
+                // Use the constant string BEARER instead of the "bearer" by hard code.
+                final String apnBearer = parser.getAttributeValue(null, BEARER);
+                if (apnBearer != null) {
+                    final int legacyBearerBitmask =
+                            getBitmaskForTech(Integer.parseInt(apnBearer));
+                    networkTypeBitmask |=
+                            convertBearerBitmaskToNetworkTypeBitmask(legacyBearerBitmask);
+                }
                 map.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
             }
             map.put(BEARER_BITMASK, bearerBitmask);
@@ -3009,10 +3023,13 @@
             // intentional fall through from above case
             case URL_PREFERAPNSET: {
                 final int set = getPreferredApnSetId(subId);
-                if (set != NO_APN_SET_ID) {
-                    constraints.add(APN_SET_ID + "=" + set);
+                if (set == NO_APN_SET_ID) {
+                    return null;
                 }
-                break;
+                constraints.add(APN_SET_ID + "=" + set);
+                qb.appendWhere(TextUtils.join(" AND ", constraints));
+                return getSubscriptionMatchingAPNList(qb, projectionIn, selection, selectionArgs,
+                        sort, subId);
             }
 
             case URL_DPC: {
@@ -3147,7 +3164,14 @@
     private Cursor getSubscriptionMatchingAPNList(SQLiteQueryBuilder qb, String[] projectionIn,
             String selection, String[] selectionArgs, String sort, int subId) {
         Cursor ret;
-        final TelephonyManager tm = ((TelephonyManager) getContext()
+        Context context = getContext();
+        SubscriptionManager subscriptionManager = (SubscriptionManager) context
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (!subscriptionManager.isActiveSubscriptionId(subId)) {
+            return null;
+        }
+
+        final TelephonyManager tm = ((TelephonyManager) context
                 .getSystemService(Context.TELEPHONY_SERVICE))
                 .createForSubscriptionId(subId);
         SQLiteDatabase db = getReadableDatabase();
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index b1cd5e4..a576c44 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -616,6 +616,35 @@
     }
 
     /**
+     * Test that crashing for one sms does not block restore of other messages.
+     * @throws Exception
+     */
+    public void testRestoreSms_WithException() throws Exception {
+        mTelephonyBackupAgent.initUnknownSender();
+        JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
+        FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows, false);
+        mMockContentResolver.addProvider("sms", smsProvider);
+        TelephonyBackupAgent.SmsProviderQuery smsProviderQuery =
+                new TelephonyBackupAgent.SmsProviderQuery() {
+                    int mIteration = 0;
+                    @Override
+                    public boolean doesSmsExist(ContentValues smsValues) {
+                        if (mIteration == 0) {
+                            mIteration++;
+                            throw new RuntimeException("fake crash for first message");
+                        }
+                        return false;
+                    }
+        };
+        mTelephonyBackupAgent.setSmsProviderQuery(smsProviderQuery);
+
+        mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
+        // the "- 1" is due to exception thrown for one of the messages
+        assertEquals(mSmsRows.length - 1, smsProvider.getRowsAdded());
+        assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
+    }
+
+    /**
      * Test restore mms with the empty json array "[]".
      * @throws Exception
      */
@@ -751,11 +780,17 @@
     private class FakeSmsProvider extends MockContentProvider {
         private int nextRow = 0;
         private ContentValues[] mSms;
+        private boolean mCheckInsertedValues = true;
 
         public FakeSmsProvider(ContentValues[] sms) {
             this.mSms = sms;
         }
 
+        public FakeSmsProvider(ContentValues[] sms, boolean checkInsertedValues) {
+            this.mSms = sms;
+            mCheckInsertedValues = checkInsertedValues;
+        }
+
         @Override
         public Uri insert(Uri uri, ContentValues values) {
             assertEquals(Telephony.Sms.CONTENT_URI, uri);
@@ -771,7 +806,7 @@
                 modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
             }
 
-            assertEquals(modifiedValues, values);
+            if (mCheckInsertedValues) assertEquals(modifiedValues, values);
             return null;
         }
 
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index 18c8d08..203848a 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -51,11 +51,8 @@
 import junit.framework.TestCase;
 
 import org.junit.Test;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.IntStream;
@@ -123,12 +120,14 @@
     private class MockContextWithProvider extends MockContext {
         private final MockContentResolver mResolver;
         private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+        private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
 
         private final List<String> GRANTED_PERMISSIONS = Arrays.asList(
                 Manifest.permission.MODIFY_PHONE_STATE, Manifest.permission.WRITE_APN_SETTINGS,
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
-        public MockContextWithProvider(TelephonyProvider telephonyProvider) {
+        public MockContextWithProvider(TelephonyProvider telephonyProvider,
+                Boolean isActiveSubscription) {
             mResolver = new MockContentResolver() {
                 @Override
                 public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
@@ -146,7 +145,8 @@
 
             // return test subId 0 for all operators
             doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator(anyInt());
-
+            doReturn(isActiveSubscription).when(mSubscriptionManager)
+                    .isActiveSubscriptionId(anyInt());
             doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
             doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator();
             doReturn(TEST_CARRIERID).when(mTelephonyManager).getSimCarrierId();
@@ -171,6 +171,9 @@
             if (name.equals(Context.TELEPHONY_SERVICE)) {
                 Log.d(TAG, "getSystemService: returning mock TM");
                 return mTelephonyManager;
+            } else if (name.equals(Context.TELEPHONY_SUBSCRIPTION_SERVICE)){
+                Log.d(TAG, "getSystemService: returning mock SubscriptionManager");
+                return mSubscriptionManager;
             } else {
                 Log.d(TAG, "getSystemService: returning null");
                 return null;
@@ -181,6 +184,8 @@
         public String getSystemServiceName(Class<?> serviceClass) {
             if (serviceClass.equals(TelephonyManager.class)) {
               return Context.TELEPHONY_SERVICE;
+            } else if (serviceClass.equals(SubscriptionManager.class)) {
+                return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
             } else {
                 Log.d(TAG, "getSystemServiceName: returning null");
                 return null;
@@ -223,12 +228,15 @@
         super.setUp();
         MockitoAnnotations.initMocks(this);
         mTelephonyProviderTestable = new TelephonyProviderTestable();
-        mContext = new MockContextWithProvider(mTelephonyProviderTestable);
-        mContentResolver = (MockContentResolver) mContext.getContentResolver();
         notifyChangeCount = 0;
         notifyChangeRestoreCount = 0;
     }
 
+    private void setUpMockContext(boolean isActiveSubId) {
+        mContext = new MockContextWithProvider(mTelephonyProviderTestable, isActiveSubId);
+        mContentResolver = mContext.getContentResolver();
+    }
+
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
@@ -242,6 +250,8 @@
     @Test
     @SmallTest
     public void testBulkInsertCarriers() {
+        setUpMockContext(true);
+
         // insert 2 test contentValues
         ContentValues contentValues = new ContentValues();
         final String insertApn = "exampleApnName";
@@ -313,6 +323,8 @@
     @Test
     @SmallTest
     public void testMccMncMigration() {
+        setUpMockContext(true);
+
         CarrierIdProviderTestable carrierIdProvider = new CarrierIdProviderTestable();
         carrierIdProvider.initializeForTesting(mContext);
         mContentResolver.addProvider(Telephony.CarrierId.All.CONTENT_URI.getAuthority(),
@@ -372,6 +384,8 @@
     @Test
     @SmallTest
     public void testUpdateConflictingCarriers() {
+        setUpMockContext(true);
+
         // insert 2 test contentValues
         ContentValues contentValues = new ContentValues();
         final String insertApn = "exampleApnName";
@@ -429,6 +443,8 @@
     }
 
     private void doSimpleTestForUri(Uri uri) {
+        setUpMockContext(true);
+
         // insert test contentValues
         ContentValues contentValues = new ContentValues();
         final String insertApn = "exampleApnName";
@@ -479,6 +495,8 @@
     @Test
     @SmallTest
     public void testOwnedBy() {
+        setUpMockContext(true);
+
         // insert test contentValues
         ContentValues contentValues = new ContentValues();
         final String insertApn = "exampleApnName";
@@ -542,6 +560,8 @@
     @Test
     @SmallTest
     public void testSimTable() {
+        setUpMockContext(true);
+
         // insert test contentValues
         ContentValues contentValues = new ContentValues();
         final int insertSubId = 11;
@@ -627,6 +647,8 @@
     @Test
     @SmallTest
     public void testEnforceManagedUri() {
+        setUpMockContext(true);
+
         mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
         final int current = 1;
@@ -749,6 +771,8 @@
      * Test URL_TELEPHONY cannot insert, query, update or delete DPC records.
      */
     public void testTelephonyUriDpcRecordAccessControl() {
+        setUpMockContext(true);
+
         mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
         final int current = 1;
@@ -825,6 +849,8 @@
     @Test
     @SmallTest
     public void testDpcUri() {
+        setUpMockContext(true);
+
         int dpcRecordId = 0, othersRecordId = 0;
         try {
             mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
@@ -916,6 +942,8 @@
     @Test
     @SmallTest
     public void testDpcUriOnConflict() {
+        setUpMockContext(true);
+
         int dpcRecordId1 = 0, dpcRecordId2 = 0;
         try {
             mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
@@ -988,6 +1016,8 @@
     @Test
     @SmallTest
     public void testAccessUrlDpcThrowSecurityExceptionFromOtherUid() {
+        setUpMockContext(true);
+
         mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID + 123456);
 
         // Test insert().
@@ -1091,6 +1121,8 @@
     }
 
     private void preserveEditedValueInMerge(int value) {
+        setUpMockContext(true);
+
         // insert user deleted APN
         String carrierName1 = "carrier1";
         String numeric1 = "123234";
@@ -1138,6 +1170,8 @@
     }
 
     private void preserveDeletedValueInMerge(int value) {
+        setUpMockContext(true);
+
         // insert user deleted APN
         String carrierName1 = "carrier1";
         String numeric1 = "123234";
@@ -1184,6 +1218,8 @@
     @Test
     @SmallTest
     public void testQueryPreferredApn() {
+        setUpMockContext(true);
+
         // create APNs
         ContentValues preferredValues = new ContentValues();
         final String preferredApn = "preferredApn";
@@ -1230,6 +1266,8 @@
     @Test
     @SmallTest
     public void testApnSetId() {
+        setUpMockContext(true);
+
         // create APNs
         ContentValues values1 = new ContentValues();
         final String apn = "apnName";
@@ -1274,6 +1312,8 @@
     @Test
     @SmallTest
     public void testPreferApnSetUrl() {
+        setUpMockContext(true);
+
         // create APNs
         ContentValues values1 = new ContentValues();
         final String apn = "apnName";
@@ -1298,20 +1338,35 @@
         values3.put(Carriers.NUMERIC, TEST_OPERATOR);
         values3.put(Carriers.APN_SET_ID, 1);
 
+        // values4 has a matching setId but it belongs to a different carrier
+        ContentValues values4 = new ContentValues();
+        final String apn4 = "fourthApnName";
+        final String name4 = "name4";
+        values4.put(Carriers.APN, apn4);
+        values4.put(Carriers.NAME, name4);
+        values4.put(Carriers.NUMERIC, "999888");
+        values4.put(Carriers.APN_SET_ID, 1);
+
         // insert APNs
         // we explicitly include subid, as SubscriptionManager.getDefaultSubscriptionId() returns -1
         Log.d(TAG, "testPreferApnSetUrl: inserting contentValues=" + values1 + ", " + values2
-                + ", " + values3);
+                + ", " + values3 + ", " + values4);
         mContentResolver.insert(CONTENT_URI_WITH_SUBID, values1);
         mContentResolver.insert(CONTENT_URI_WITH_SUBID, values2);
+        mContentResolver.insert(CONTENT_URI_WITH_SUBID, values4);
         Uri uri = mContentResolver.insert(CONTENT_URI_WITH_SUBID, values3);
 
-        // before there's a preferred APN set, assert that all APNs are returned
+        // verify all APNs were correctly inserted
         final String[] testProjection = { Carriers.NAME };
         Cursor cursor = mContentResolver.query(
+                Carriers.CONTENT_URI, testProjection, null, null, null);
+        assertEquals(4, cursor.getCount());
+
+        // preferapnset/subId returns null when there is no preferred APN
+        cursor = mContentResolver.query(
                 Uri.withAppendedPath(Carriers.CONTENT_URI, "preferapnset/subId/" + TEST_SUBID),
                 testProjection, null, null, null);
-        assertEquals(3, cursor.getCount());
+        assertNull(cursor);
 
         // set the APN from values3 (apn_set_id = 1) to the preferred APN
         final String preferredApnIdString = uri.getLastPathSegment();
@@ -1326,6 +1381,7 @@
         cursor = mContentResolver.query(
                 Uri.withAppendedPath(Carriers.CONTENT_URI, "preferapnset/subId/" + TEST_SUBID),
                 testProjection, null, null, null);
+        // values4 which was inserted with a different carrier is not included in the results
         assertEquals(2, cursor.getCount());
         cursor.moveToFirst();
         assertEquals(name2, cursor.getString(0));
@@ -1339,6 +1395,8 @@
     @Test
     @SmallTest
     public void testRestoreDefaultApn() {
+        setUpMockContext(true);
+
         // setup for multi-SIM
         TelephonyManager telephonyManager =
                 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -1435,6 +1493,8 @@
     @Test
     @SmallTest
     public void testUpdateWfcEnabled() {
+        setUpMockContext(true);
+
         // insert test contentValues
         ContentValues contentValues = new ContentValues();
         final int insertSubId = 1;
@@ -1481,6 +1541,8 @@
     @Test
     @SmallTest
     public void testSIMAPNLIST_MatchTheMVNOAPN() {
+        setUpMockContext(true);
+
         // Test on getSubscriptionMatchingAPNList() step 1
         final String apnName = "apnName";
         final String carrierName = "name";
@@ -1519,6 +1581,7 @@
                         Carriers.NUMERIC,
                         Carriers.MVNO_MATCH_DATA
                 };
+
         Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
                 testProjection, null, null, null);
 
@@ -1534,6 +1597,8 @@
     @Test
     @SmallTest
     public void testSIMAPNLIST_MatchTheMNOAPN() {
+        setUpMockContext(true);
+
         // Test on getSubscriptionMatchingAPNList() step 2
         final String apnName = "apnName";
         final String carrierName = "name";
@@ -1553,6 +1618,7 @@
                         Carriers.NAME,
                         Carriers.NUMERIC,
                 };
+
         Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
                 testProjection, null, null, null);
 
@@ -1565,6 +1631,8 @@
     @Test
     @SmallTest
     public void testSIMAPNLIST_MatchTheCarrierIDANDMNOAPN() {
+        setUpMockContext(true);
+
         // Test on getSubscriptionMatchingAPNList() will return the {MCCMNC}
         final String apnName = "apnName";
         final String carrierName = "name";
@@ -1592,6 +1660,7 @@
                 Carriers.NAME,
                 Carriers.CARRIER_ID,
             };
+
         Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST, testProjection, null, null, null);
 
         // The query based on SIM_APN_LIST will return MNO APN and the APN that has carrier id
@@ -1601,6 +1670,8 @@
     @Test
     @SmallTest
     public void testSIMAPNLIST_MatchTheCarrierAPNAndMVNOAPN() {
+        setUpMockContext(true);
+
         final String apnName = "apnName";
         final String carrierName = "name";
         final String mvnoType = "spn";
@@ -1638,6 +1709,7 @@
                 Carriers.CARRIER_ID,
                 Carriers.MVNO_TYPE,
             };
+
         Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
             testProjection, null, null, null);
 
@@ -1648,4 +1720,34 @@
                     || !TextUtils.isEmpty(cursor.getString(3)));
         }
     }
+
+    @Test
+    @SmallTest
+    public void testSIMAPNLIST_isNotActiveSubscription() {
+        setUpMockContext(false);
+
+        // Test on getSubscriptionMatchingAPNList() step 2
+        final String apnName = "apnName";
+        final String carrierName = "name";
+        final String numeric = TEST_OPERATOR;
+
+        // Insert the MNO APN
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Carriers.APN, apnName);
+        contentValues.put(Carriers.NAME, carrierName);
+        contentValues.put(Carriers.NUMERIC, numeric);
+        mContentResolver.insert(Carriers.CONTENT_URI, contentValues);
+
+        // Query DB
+        final String[] testProjection =
+                {
+                        Carriers.APN,
+                        Carriers.NAME,
+                        Carriers.NUMERIC,
+                };
+        Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
+                testProjection, null, null, null);
+
+        assertNull(cursor);
+    }
 }