Merge tag 'LA.UM.5.8.r1-02900-8x98.0' into int/n/fp2

"LA.UM.5.8.r1-02900-8x98.0"

* tag 'LA.UM.5.8.r1-02900-8x98.0': (21 commits)
  Fix NPE for restoring default APN
  30481342: Security Vulnerability - TOCTOU in MmsProvider allows access to files as phone (radio) uid
  Fix apn restore issue for multi-sim.
  Fix apn overwrite issue
  Enable messaging search based on conversation
  Messaging FC when sharing long name file
  Android M sytle upgrade : P3 feature
  Mms: Optimize the backup&restore MMS performance
  TelephonyProvider: Improve the performance of deleting Mms.
  TelephonyProvider: Implement inserting SMS into ICC card by provider
  APN: Add the read only APN feature.
  Mms: Fix Message thread time display as current time after restore
  Mms: Support SMS full indicator
  SMS: Support priority for SMS
  Mms: Support query and delete multi SIM card message
  android.intent.action.CONTENT_CHANGED should be system protected
  android.intent.action.CONTENT_CHANGED should be system protected
  Add message_body to raw table when it is created.
  Temporary changes to disable sms de-duping code.
  Temporary changes to disable sms de-duping code.
  ...

Issue: FP2N-105
Change-Id: I981e26289e8406652f1d660f866f5e442d9394cb
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
old mode 100644
new mode 100755
index 71e84c6..79791fa
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.ContentProvider;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 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;
@@ -45,12 +47,21 @@
 import android.text.TextUtils;
 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,
@@ -67,6 +78,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() {
@@ -86,6 +106,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) {
@@ -217,6 +286,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;
@@ -300,6 +382,119 @@
         }
     }
 
+    private byte[] getPduDataFromFile(String pduPath) {
+        FileInputStream fileInputStream = null;
+        byte[] data = null;
+        try {
+            File pduFile = new File(pduPath);
+            data = new byte[(int)pduFile.length()];
+            fileInputStream = new FileInputStream(pduFile);
+            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) {
+        Uri msgUri = null;
+        if (uri == null || TextUtils.isEmpty(pduPath)) {
+            return null;
+        }
+
+        try {
+            byte[] pduData = getPduDataFromFile(pduPath);
+            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) {
+        int count = 0;
+        Uri msgUri = restorePduFile(uri, getPduPath(dir, values));
+        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");
+        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);
+            }
+
+            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) {
         // The _data column is filled internally in MmsProvider, so this check is just to avoid
@@ -451,7 +646,7 @@
                 String contentLocation = values.getAsString("cl");
                 if (!TextUtils.isEmpty(contentLocation)) {
                     File f = new File(contentLocation);
-                    contentLocation = "_" + f.getName();
+                    contentLocation = "_" + getFileName(contentLocation);
                 } else {
                     contentLocation = "";
                 }
@@ -558,6 +753,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:
@@ -642,6 +846,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 {
@@ -656,12 +862,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;
@@ -670,12 +877,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);
@@ -687,6 +900,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);
@@ -976,6 +1201,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);
@@ -1002,6 +1228,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
index 9218e64..93eea61 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -23,6 +23,7 @@
 import android.content.IntentFilter;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.storage.StorageManager;
 import android.provider.BaseColumns;
@@ -93,19 +94,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" +
@@ -155,21 +143,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.
@@ -195,28 +168,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 =
@@ -234,12 +185,14 @@
     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 = 65;
     private final Context mContext;
     private LowStorageMonitor mLowStorageMonitor;
 
-
     private MmsSmsDatabaseHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
 
@@ -417,6 +370,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 void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) {
         db.beginTransaction();
         try {
@@ -487,6 +488,39 @@
         createIndices(db);
     }
 
+    @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");
+    }
+
     // When upgrading the database we need to populate the words
     // table with the rows out of sms and part.
     private void populateWordsTable(SQLiteDatabase db) {
@@ -581,8 +615,18 @@
 
     private void createIndices(SQLiteDatabase db) {
         createThreadIdIndex(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" +
@@ -714,9 +758,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);
 
@@ -792,10 +833,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" +
@@ -804,18 +841,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 " +
@@ -885,7 +910,8 @@
                    "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
                    "error_code INTEGER DEFAULT 0," +
                    "creator TEXT," +
-                   "seen INTEGER DEFAULT 0" +
+                   "seen INTEGER DEFAULT 0," +
+                   "priority INTEGER DEFAULT -1" +
                    ");");
 
         /**
@@ -966,7 +992,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.
@@ -1519,9 +1547,8 @@
 
         updateThreadsAttachmentColumn(db);
 
-        // Add insert and delete triggers for keeping it up to date.
+        // Add insert 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) {
@@ -1747,6 +1774,42 @@
     private void upgradeDatabaseToVersion64(SQLiteDatabase db) {
         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN message_body TEXT");
     }
+    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");
+            }
+        }
+    }
 
     private void upgradeDatabaseToVersion65(SQLiteDatabase db) {
         db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW + " ADD COLUMN display_originating_addr TEXT");
@@ -1916,7 +1979,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
index d5e0ef9..7b44583 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -99,7 +99,15 @@
     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;
+    // 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.
@@ -110,6 +118,7 @@
      * the name of the table that is used to store the canonical addresses for both SMS and MMS.
      */
     private 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.
@@ -252,9 +261,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);
@@ -264,6 +278,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);
 
@@ -355,6 +371,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,
@@ -462,6 +480,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");
@@ -585,7 +606,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);
@@ -984,6 +1005,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.
      *
@@ -1286,8 +1318,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:
@@ -1313,6 +1351,9 @@
                 break;
             }
 
+            case URI_UPDATE_THREAD_DATE:
+                MmsSmsDatabaseHelper.updateThreadsDate(db, selection, selectionArgs);
+                break;
             default:
                 throw new UnsupportedOperationException(
                         NO_DELETES_INSERTS_OR_UPDATES + uri);
@@ -1389,4 +1430,281 @@
         }
         writer.println("Default SMS app: " + defaultSmsApp);
     }
+
+    private Cursor getSearchMessages(Uri uri, SQLiteDatabase db,
+                                     String smsTable, String pduTable) {
+        int searchMode = Integer.parseInt(uri.getQueryParameter("search_mode"));
+        String keyStr = uri.getQueryParameter("key_str");
+        String address = uri.getQueryParameter("contact_addr");
+        String searchString = "%" + addEscapeCharacter(keyStr) + "%";
+        String threadIdString = DEFAULT_STRING_ZERO;
+
+        if (searchMode == SEARCH_MODE_NAME) {
+            threadIdString = getThreadIdByAddress(address);
+            if (threadIdString.equals(DEFAULT_STRING_ZERO)) {
+                searchMode = SEARCH_MODE_CONTENT;
+            }
+        }
+        if (DEBUG) {
+            Log.d(LOG_TAG, "keystr=" + searchString +
+                    "|searchMode=" + searchMode + "|address=" + address);
+        }
+        String rawQuery = getConversationQueryString(searchMode, smsTable,
+                pduTable, threadIdString);
+        String[] strArray = new String[]{searchString, searchString, searchString,
+                searchString, searchString};
+        return db.rawQuery(rawQuery, strArray);
+    }
+
+    private String getThreadIdByAddress(String keyStr) {
+        long[] addressIdSet = getSortedSet(getThreadIdsByAddressList(keyStr.split(",")));
+        String threadIdString = getCommaSeparatedId(addressIdSet);
+        if (TextUtils.isEmpty(threadIdString)) {
+            threadIdString = DEFAULT_STRING_ZERO;
+        }
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getThreadIdByAddress=" + threadIdString);
+        }
+        return threadIdString;
+    }
+
+    private String addEscapeCharacter(String keyStr) {
+        if (keyStr == null) {
+            return keyStr;
+        }
+        if (keyStr.contains("%") ||
+                keyStr.contains(String.valueOf(SEARCH_ESCAPE_CHARACTER))) {
+            StringBuilder searchKeyStrBuilder = new StringBuilder();
+            int keyStrLen = keyStr.length();
+            for (int i = 0; i < keyStrLen; i++) {
+                if (keyStr.charAt(i) == '%' ||
+                        keyStr.charAt(i) == SEARCH_ESCAPE_CHARACTER) {
+                    searchKeyStrBuilder.append(SEARCH_ESCAPE_CHARACTER);
+                    searchKeyStrBuilder.append(keyStr.charAt(i));
+                    continue;
+                }
+                searchKeyStrBuilder.append(keyStr.charAt(i));
+            }
+            return searchKeyStrBuilder.toString();
+        }
+        return keyStr;
+    }
+
+    private Set<Long> getThreadIdsByAddressList(String[] addresses) {
+        int count = addresses.length;
+        Set<Long> result = new HashSet<Long>(count);
+
+        for (int i = 0; i < count; i++) {
+            String address = addresses[i];
+            if (address != null && !address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+                long id = getSingleThreadId(address);
+                if (id != RESULT_FOR_ID_NOT_FOUND) {
+                    result.add(id);
+                } else {
+                    Log.e(LOG_TAG, "Address ID not found for: " + address);
+                }
+            }
+        }
+        return result;
+    }
+
+    private String getCommaSeparatedId(long[] addrIds) {
+        int size = addrIds.length;
+        StringBuilder buffer = new StringBuilder();
+
+        for (int i = 0; i < size; i++) {
+            if (i != 0) {
+                buffer.append(',');
+            }
+            buffer.append(getThreadIds(String.valueOf(addrIds[i])));
+        }
+        return buffer.toString();
+    }
+
+    private long getSingleThreadId(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};
+        }
+
+        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 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;
+    }
+
+    private synchronized String getThreadIds(String recipientIds) {
+        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 + " %'";
+        String resultString = DEFAULT_STRING_ZERO;
+        StringBuilder buffer = new StringBuilder();
+
+        if (DEBUG) {
+            Log.v(LOG_TAG, "getThreadId THREAD_QUERY: " + THREAD_QUERY +
+                    ", recipientIds=" + recipientIds);
+        }
+        Cursor cursor = null;
+        try {
+            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+            cursor = db.rawQuery(THREAD_QUERY, new String[]{
+                    recipientIds
+            });
+
+            if (cursor == null || cursor.getCount() == 0) {
+                return resultString;
+            }
+            int i = 0;
+            while (cursor.moveToNext()) {
+                if (i != 0) {
+                    buffer.append(',');
+                }
+                buffer.append(String.valueOf(cursor.getLong(0)));
+                i++;
+            }
+            resultString = buffer.toString();
+
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return resultString;
+    }
 }
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index f50f804..2081756 100644
--- 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;
@@ -38,13 +44,30 @@
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.TextBasedSmsColumns;
 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 java.util.ArrayList;
 import java.util.HashMap;
+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 {
     private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
@@ -55,7 +78,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 };
@@ -64,6 +96,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 Uri INSERT_SMS_INTO_ICC_SUCCESS = Uri.parse("content://iccsms/success");
+    private static final Uri INSERT_SMS_INTO_ICC_FAIL = Uri.parse("content://iccsms/fail");
+
     /**
      * These are the columns that are available when reading SMS
      * messages from the ICC.  Columns whose names begin with "is_"
@@ -84,7 +120,8 @@
         "type",                         // Always MESSAGE_TYPE_ALL.
         "locked",                       // Always 0 (false).
         "error_code",                   // Always 0
-        "_id"
+        "_id",
+        "sub_id"
     };
 
     @Override
@@ -241,12 +278,29 @@
                 break;
 
             case SMS_ALL_ICC:
-                return getAllMessagesFromIcc();
+                return getAllMessagesFromIcc(SubscriptionManager.getDefaultSmsSubscriptionId());
 
             case SMS_ICC:
-                String messageIndexString = url.getPathSegments().get(1);
+                String messageIndexIcc = url.getPathSegments().get(MESSAGE_ID);
 
-                return getSingleMessageFromIcc(messageIndexString);
+                return getSingleMessageFromIcc(messageIndexIcc,
+                        SubscriptionManager.getDefaultSmsSubscriptionId());
+
+            case SMS_ALL_ICC1:
+                return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT1)[0]);
+
+            case SMS_ICC1:
+                String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+                return getSingleMessageFromIcc(messageIndexIcc1,
+                        SubscriptionManager.getSubId(SLOT1)[0]);
+
+            case SMS_ALL_ICC2:
+                return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT2)[0]);
+
+            case SMS_ICC2:
+                String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+                return getSingleMessageFromIcc(messageIndexIcc2,
+                        SubscriptionManager.getSubId(SLOT2)[0]);
 
             default:
                 Log.e(TAG, "Invalid request: " + url);
@@ -286,42 +340,56 @@
         return mCeOpenHelper;
     }
 
-    private Object[] convertIccToSms(SmsMessage message, int id) {
+    private Object[] convertIccToSms(SmsMessage message, int id, int subId) {
         // N.B.: These calls must appear in the same order as the
         // columns appear in ICC_COLUMNS.
-        Object[] row = new Object[13];
+        int statusOnIcc = message.getStatusOnIcc();
+        int type = Sms.MESSAGE_TYPE_ALL;
+        switch (statusOnIcc) {
+            case SmsManager.STATUS_ON_ICC_READ:
+            case SmsManager.STATUS_ON_ICC_UNREAD:
+                type = Sms.MESSAGE_TYPE_INBOX;
+                break;
+            case SmsManager.STATUS_ON_ICC_SENT:
+                type = Sms.MESSAGE_TYPE_SENT;
+                break;
+            case SmsManager.STATUS_ON_ICC_UNSENT:
+                type = Sms.MESSAGE_TYPE_OUTBOX;
+                break;
+        }
+        Object[] row = new Object[14];
         row[0] = message.getServiceCenterAddress();
-        row[1] = message.getDisplayOriginatingAddress();
+        row[1] = (type == Sms.MESSAGE_TYPE_INBOX) ? message.getDisplayOriginatingAddress()
+                : message.getRecipientAddress();
         row[2] = String.valueOf(message.getMessageClass());
         row[3] = message.getDisplayMessageBody();
         row[4] = message.getTimestampMillis();
-        row[5] = Sms.STATUS_NONE;
+        row[5] = statusOnIcc;
         row[6] = message.getIndexOnIcc();
         row[7] = message.isStatusReportMessage();
         row[8] = "sms";
-        row[9] = TextBasedSmsColumns.MESSAGE_TYPE_ALL;
+        row[9] = type;
         row[10] = 0;      // locked
         row[11] = 0;      // error_code
         row[12] = id;
+        row[13] = subId;
         return row;
     }
 
     /**
      * Return a Cursor containing just one message from the ICC.
      */
-    private Cursor getSingleMessageFromIcc(String messageIndexString) {
+    private Cursor getSingleMessageFromIcc(String messageIndexString, int subId) {
+        ArrayList<SmsMessage> messages;
         int messageIndex = -1;
         try {
             Integer.parseInt(messageIndexString);
         } catch (NumberFormatException exception) {
             throw new IllegalArgumentException("Bad SMS ICC ID: " + messageIndexString);
         }
-        ArrayList<SmsMessage> messages;
-        final SmsManager smsManager = SmsManager.getDefault();
-        // Use phone id to avoid AppOps uid mismatch in telephony
         long token = Binder.clearCallingIdentity();
         try {
-            messages = smsManager.getAllMessagesFromIcc();
+            messages = SmsManager.getSmsManagerForSubscriptionId(subId).getAllMessagesFromIcc();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -334,21 +402,21 @@
                     "Message not retrieved. ID: " + messageIndexString);
         }
         MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1);
-        cursor.addRow(convertIccToSms(message, 0));
+        cursor.addRow(convertIccToSms(message, 0, subId));
         return withIccNotificationUri(cursor);
     }
 
     /**
      * Return a Cursor listing all the messages stored on the ICC.
      */
-    private Cursor getAllMessagesFromIcc() {
-        SmsManager smsManager = SmsManager.getDefault();
+    private Cursor getAllMessagesFromIcc(int subId) {
         ArrayList<SmsMessage> messages;
 
         // use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call
         long token = Binder.clearCallingIdentity();
         try {
-            messages = smsManager.getAllMessagesFromIcc();
+            messages = SmsManager.getSmsManagerForSubscriptionId(subId)
+                    .getAllMessagesFromIcc();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -358,7 +426,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 withIccNotificationUri(cursor);
@@ -516,6 +584,9 @@
                 table = "canonical_addresses";
                 break;
 
+            case SMS_ALL_ICC:
+                return insertMessageIntoIcc(initialValues);
+
             default:
                 Log.e(TAG, "Invalid request: " + url);
                 return null;
@@ -645,6 +716,338 @@
         return null;
     }
 
+    private Uri insertMessageIntoIcc(ContentValues values) {
+        if (values == null) {
+            return 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 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);
+        }
+    }
+
     @Override
     public int delete(Uri url, String where, String[] whereArgs) {
         int count;
@@ -710,9 +1113,20 @@
                 break;
 
             case SMS_ICC:
-                String messageIndexString = url.getPathSegments().get(1);
+                String messageIndexIcc = url.getPathSegments().get(MESSAGE_ID);
 
-                return deleteMessageFromIcc(messageIndexString);
+                return deleteMessageFromIcc(messageIndexIcc,
+                        SubscriptionManager.getDefaultSmsSubscriptionId());
+
+            case SMS_ICC1:
+                String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+                return deleteMessageFromIcc(messageIndexIcc1,
+                        SubscriptionManager.getSubId(SLOT1)[0]);
+
+            case SMS_ICC2:
+                String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+                return deleteMessageFromIcc(messageIndexIcc2,
+                        SubscriptionManager.getSubId(SLOT2)[0]);
 
             default:
                 throw new IllegalArgumentException("Unknown URL");
@@ -728,14 +1142,12 @@
      * Delete the message at index from ICC.  Return true iff
      * successful.
      */
-    private int deleteMessageFromIcc(String messageIndexString) {
-        SmsManager smsManager = SmsManager.getDefault();
-        // Use phone id to avoid AppOps uid mismatch in telephony
+    private int deleteMessageFromIcc(String messageIndexString, int subId) {
         long token = Binder.clearCallingIdentity();
         try {
-            return smsManager.deleteMessageFromIcc(
+            return SmsManager.getSmsManagerForSubscriptionId(subId).deleteMessageFromIcc(
                     Integer.parseInt(messageIndexString))
-                    ? 1 : 0;
+                    ? DELETE_SUCCESS : DELETE_FAIL;
         } catch (NumberFormatException exception) {
             throw new IllegalArgumentException(
                     "Bad SMS ICC ID: " + messageIndexString);
@@ -884,6 +1296,11 @@
     private static final int SMS_UNDELIVERED = 27;
     private static final int SMS_RAW_MESSAGE_PERMANENT_DELETE = 28;
 
+    private static final int SMS_ALL_ICC1 = 29;
+    private static final int SMS_ICC1 = 30;
+    private static final int SMS_ALL_ICC2 = 31;
+    private static final int SMS_ICC2 = 32;
+
     private static final UriMatcher sURLMatcher =
             new UriMatcher(UriMatcher.NO_MATCH);
 
@@ -914,6 +1331,10 @@
         sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
         sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC);
         sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
+        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/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 4081bb7..55da5bc 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -107,6 +107,8 @@
     private static final String OTA_UPDATED_APNS_PATH = "misc/apns-conf.xml";
     private static final String OLD_APNS_PATH = "etc/old-apns-conf.xml";
 
+    private static final String READ_ONLY = "read_only";
+
     private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
     private static final ContentValues s_currentNullMap;
@@ -150,6 +152,7 @@
         CARRIERS_UNIQUE_FIELDS.add(MVNO_TYPE);
         CARRIERS_UNIQUE_FIELDS.add(MVNO_MATCH_DATA);
         CARRIERS_UNIQUE_FIELDS.add(PROFILE_ID);
+        CARRIERS_UNIQUE_FIELDS.add(TYPE);
     }
 
     static {
@@ -315,6 +318,7 @@
                     MTU + " INTEGER DEFAULT 0," +
                     EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
                     USER_VISIBLE + " BOOLEAN DEFAULT 1," +
+                    READ_ONLY + " BOOLEAN DEFAULT 0," +
                     // Uniqueness collisions are used to trigger merge code so if a field is listed
                     // here it means we will accept both (user edited + new apn_conf definition)
                     // Columns not included in UNIQUE constraint: name, current, edited,
@@ -1101,6 +1105,7 @@
             addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED);
             addBoolAttribute(parser, "modem_cognitive", map, MODEM_COGNITIVE);
             addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
+            addBoolAttribute(parser, "read_only", map, READ_ONLY);
 
             int bearerBitmask = 0;
             String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
@@ -2235,9 +2240,38 @@
 
     private void restoreDefaultAPN(int subId) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        TelephonyManager mTm =
+               (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        SubscriptionManager sm = SubscriptionManager.from(getContext());
+        String selSubOperatorNumeric = mTm.getSimOperator(subId);
+        String otherSubOperatorNumeric = null;
+        String where = null;
+        List<SubscriptionInfo> subInfoList = sm.getActiveSubscriptionInfoList();
+        int simCountWithSameNumeric = 0;
+        if (subInfoList != null && subInfoList.size() > 1) {
+            where = "not (";
+            for (SubscriptionInfo subInfo : subInfoList) {
+               if (subId != subInfo.getSubscriptionId()) {
+                  otherSubOperatorNumeric = mTm.getSimOperator(
+                          subInfo.getSubscriptionId());
+                  if (!otherSubOperatorNumeric.equalsIgnoreCase(selSubOperatorNumeric)) {
+                      where = where + "numeric=" + otherSubOperatorNumeric + " and ";
+                  } else {
+                      simCountWithSameNumeric++;
+                  }
+               }
+            }
+            where = where + "edited=" + USER_EDITED + ")";
+        }
+
+        if (subInfoList != null && simCountWithSameNumeric == subInfoList.size() - 1) {
+            //Reset where as all slots have same sims
+            where = null;
+        }
+        log("restoreDefaultAPN: where: " + where);
 
         try {
-            db.delete(CARRIERS_TABLE, null, null);
+            db.delete(CARRIERS_TABLE, where, null);
         } catch (SQLException e) {
             loge("got exception when deleting to restore: " + e);
         }