Restricted view of SMS/MMS providers (2/2)

Only expose sent/received SMS/MMS and non-wap push MMS messages for
non-default SMS apps, preventing non-default SMS apps to interfere
internal states of default SMS app, like stealing wap push to download
or sending outbox message.

b/19348537

Change-Id: Icec639a01ab8f5cd2d1346b76418ec487979295a
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 6b5b29e..710fa43 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -62,6 +62,7 @@
     static final String TABLE_RATE = "rate";
     static final String TABLE_DRM  = "drm";
     static final String TABLE_WORDS = "words";
+    static final String VIEW_PDU_RESTRICTED = "pdu_restricted";
 
     // The name of parts directory. The full dir is "app_parts".
     private static final String PARTS_DIR_NAME = "parts";
@@ -73,9 +74,27 @@
         return true;
     }
 
+    /**
+     * Return the proper view of "pdu" table for the current access status.
+     *
+     * @param accessRestricted If the access is restricted
+     * @return the table/view name of the mms data
+     */
+    public static String getPduTable(boolean accessRestricted) {
+        return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU;
+    }
+
     @Override
     public Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
+        // First check if a restricted view of the "pdu" table should be used based on the
+        // caller's identity. Only system, phone or the default sms app can have full access
+        // of mms data. For other apps, we present a restricted view which only contains sent
+        // or received messages, without wap pushes.
+        final boolean accessRestricted = ProviderUtil.isAccessRestricted(
+                getContext(), getCallingPackage(), Binder.getCallingUid());
+        final String pduTable = getPduTable(accessRestricted);
+
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 
         // Generate the body of the query.
@@ -86,29 +105,29 @@
 
         switch (match) {
             case MMS_ALL:
-                constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL);
+                constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL, pduTable);
                 break;
             case MMS_INBOX:
-                constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX);
+                constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX, pduTable);
                 break;
             case MMS_SENT:
-                constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT);
+                constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT, pduTable);
                 break;
             case MMS_DRAFTS:
-                constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS);
+                constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS, pduTable);
                 break;
             case MMS_OUTBOX:
-                constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX);
+                constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX, pduTable);
                 break;
             case MMS_ALL_ID:
-                qb.setTables(TABLE_PDU);
+                qb.setTables(pduTable);
                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
                 break;
             case MMS_INBOX_ID:
             case MMS_SENT_ID:
             case MMS_DRAFTS_ID:
             case MMS_OUTBOX_ID:
-                qb.setTables(TABLE_PDU);
+                qb.setTables(pduTable);
                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
                 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
                         + getMessageBoxByMatch(match));
@@ -155,19 +174,23 @@
                    OR (msg_id = id3 AND type = 137)
                    WHERE T.id1 = ?;
                  */
-                qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" +
-                             " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" +
-                             " delivery_status, ifnull(P3.read_status, 0) AS" +
-                             " read_status FROM pdu P1 INNER JOIN pdu P2 ON" +
-                             " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" +
-                             " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" +
-                             " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" +
-                             " AS id3, ifnull(P2.st, 0) AS delivery_status," +
-                             " ifnull(P3.read_status, 0) AS read_status FROM" +
-                             " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" +
-                             " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" +
-                             " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" +
-                             " OR (msg_id=id3 AND type=137)");
+                qb.setTables(TABLE_ADDR + " INNER JOIN "
+                        + "(SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
+                        + "ifnull(P2.st, 0) AS delivery_status, "
+                        + "ifnull(P3.read_status, 0) AS read_status "
+                        + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P2 "
+                        + "ON P1.m_id=P2.m_id AND P2.m_type=134 "
+                        + "LEFT JOIN " + pduTable + " P3 "
+                        + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
+                        + "UNION "
+                        + "SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
+                        + "ifnull(P2.st, 0) AS delivery_status, "
+                        + "ifnull(P3.read_status, 0) AS read_status "
+                        + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P3 "
+                        + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
+                        + "LEFT JOIN " + pduTable + " P2 "
+                        + "ON P1.m_id=P2.m_id AND P2.m_type=134) T "
+                        + "ON (msg_id=id2 AND type=151) OR (msg_id=id3 AND type=137)");
                 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
                 qb.setDistinct(true);
                 break;
@@ -178,9 +201,9 @@
                    WHERE pdu._id = messageId AND addr.type = 151
                  */
                 qb.setTables(TABLE_ADDR + " join " +
-                        TABLE_PDU + " on pdu._id = addr.msg_id");
-                qb.appendWhere("pdu._id = " + uri.getLastPathSegment());
-                qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO);
+                        pduTable + " on " + pduTable + "._id = addr.msg_id");
+                qb.appendWhere(pduTable + "._id = " + uri.getLastPathSegment());
+                qb.appendWhere(" AND " + TABLE_ADDR + ".type = " + PduHeaders.TO);
                 break;
             case MMS_SENDING_RATE:
                 qb.setTables(TABLE_RATE);
@@ -190,7 +213,7 @@
                 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
                 break;
             case MMS_THREADS:
-                qb.setTables("pdu group by thread_id");
+                qb.setTables(pduTable + " group by thread_id");
                 break;
             default:
                 Log.e(TAG, "query: invalid request: " + uri);
@@ -199,7 +222,7 @@
 
         String finalSortOrder = null;
         if (TextUtils.isEmpty(sortOrder)) {
-            if (qb.getTables().equals(TABLE_PDU)) {
+            if (qb.getTables().equals(pduTable)) {
                 finalSortOrder = Mms.DATE + " DESC";
             } else if (qb.getTables().equals(TABLE_PART)) {
                 finalSortOrder = Part.SEQ;
@@ -223,8 +246,8 @@
         return ret;
     }
 
-    private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) {
-        qb.setTables(TABLE_PDU);
+    private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable) {
+        qb.setTables(pduTable);
 
         if (msgBox != Mms.MESSAGE_BOX_ALL) {
             qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
@@ -282,6 +305,7 @@
             return null;
         }
         final int callerUid = Binder.getCallingUid();
+        final String callerPkg = getCallingPackage();
         int msgBox = Mms.MESSAGE_BOX_ALL;
         boolean notify = true;
 
@@ -379,8 +403,7 @@
                 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
                 // set CREATOR using the truth on caller.
                 // Note: Inferring package name from UID may include unrelated package names
-                finalValues.put(Telephony.Mms.CREATOR,
-                        ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
+                finalValues.put(Telephony.Mms.CREATOR, callerPkg);
             }
 
             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
@@ -709,6 +732,7 @@
             return 0;
         }
         final int callerUid = Binder.getCallingUid();
+        final String callerPkg = getCallingPackage();
         int match = sURLMatcher.match(uri);
         if (LOCAL_LOGV) {
             Log.v(TAG, "Update uri=" + uri + ", match=" + match);
@@ -763,8 +787,7 @@
             filterUnsupportedKeys(values);
             if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
                 // CREATOR should not be changed by non-SYSTEM/PHONE apps
-                Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
-                        " tries to update CREATOR");
+                Log.w(TAG, callerPkg + " tries to update CREATOR");
                 values.remove(Mms.CREATOR);
             }
             finalValues = new ContentValues(values);
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 6325905..e763f35 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -16,14 +16,6 @@
 
 package com.android.providers.telephony;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -35,19 +27,27 @@
 import android.provider.BaseColumns;
 import android.provider.Telephony;
 import android.provider.Telephony.Mms;
-import android.provider.Telephony.MmsSms;
-import android.provider.Telephony.Sms;
-import android.provider.Telephony.Threads;
 import android.provider.Telephony.Mms.Addr;
 import android.provider.Telephony.Mms.Part;
 import android.provider.Telephony.Mms.Rate;
+import android.provider.Telephony.MmsSms;
 import android.provider.Telephony.MmsSms.PendingMessages;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
 import com.google.android.mms.pdu.EncodedStringValue;
 import com.google.android.mms.pdu.PduHeaders;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+
 public class MmsSmsDatabaseHelper extends SQLiteOpenHelper {
     private static final String TAG = "MmsSmsDatabaseHelper";
 
@@ -216,7 +216,7 @@
     private static boolean sFakeLowStorageTest = false;     // for testing only
 
     static final String DATABASE_NAME = "mmssms.db";
-    static final int DATABASE_VERSION = 60;
+    static final int DATABASE_VERSION = 61;
     private final Context mContext;
     private LowStorageMonitor mLowStorageMonitor;
 
@@ -630,6 +630,15 @@
         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" +
                    BaseColumns._ID + " INTEGER PRIMARY KEY," +
                    "_data TEXT);");
+
+        // Restricted view of pdu table, only sent/received messages without wap pushes
+        db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + " AS " +
+                "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " +
+                "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX +
+                " OR " +
+                Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" +
+                " AND " +
+                "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");");
     }
 
     // Unlike the other trigger-creating functions, this function can be called multiple times
@@ -873,6 +882,13 @@
                    "reference_number INTEGER," +
                    "action TEXT," +
                    "data TEXT);");
+
+        // Restricted view of sms table, only sent/received messages
+        db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " +
+                   "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " +
+                   Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX +
+                   " OR " +
+                   Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
     }
 
     private void createCommonTables(SQLiteDatabase db) {
@@ -1331,6 +1347,22 @@
             } finally {
                 db.endTransaction();
             }
+            // fall through
+        case 60:
+            if (currentVersion <= 60) {
+                return;
+            }
+
+            db.beginTransaction();
+            try {
+                upgradeDatabaseToVersion61(db);
+                db.setTransactionSuccessful();
+            } catch (Throwable ex) {
+                Log.e(TAG, ex.getMessage(), ex);
+                break;
+            } finally {
+                db.endTransaction();
+            }
             return;
         }
 
@@ -1559,6 +1591,22 @@
                 + Threads.ARCHIVED + " INTEGER DEFAULT 0");
     }
 
+    private void upgradeDatabaseToVersion61(SQLiteDatabase db) {
+        db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " +
+                   "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " +
+                   Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX +
+                   " OR " +
+                   Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
+        db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + "  AS " +
+                   "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " +
+                   "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX +
+                   " OR " +
+                   Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" +
+                   " AND " +
+                   "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");");
+
+    }
+
     @Override
     public synchronized SQLiteDatabase getWritableDatabase() {
         SQLiteDatabase db = super.getWritableDatabase();
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 457e472..70d0dde 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -187,32 +187,50 @@
             Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " +
             Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))";
 
-    // Search on the words table but return the rows from the corresponding sms table
-    private static final String SMS_QUERY =
-            "SELECT sms._id AS _id,thread_id,address,body,date,date_sent,index_text,words._id " +
-            "FROM sms,words WHERE (index_text MATCH ? " +
-            "AND sms._id=words.source_id AND words.table_to_use=1)";
+    private static String getTextSearchQuery(String smsTable, String pduTable) {
+        // Search on the words table but return the rows from the corresponding sms table
+        final String smsQuery = "SELECT "
+                + smsTable + "._id AS _id,"
+                + "thread_id,"
+                + "address,"
+                + "body,"
+                + "date,"
+                + "date_sent,"
+                + "index_text,"
+                + "words._id "
+                + "FROM " + smsTable + ",words "
+                + "WHERE (index_text MATCH ? "
+                + "AND " + smsTable + "._id=words.source_id "
+                + "AND words.table_to_use=1)";
 
-    // Search on the words table but return the rows from the corresponding parts table
-    private static final String MMS_QUERY =
-            "SELECT pdu._id,thread_id,addr.address,part.text " +
-            "AS body,pdu.date,pdu.date_sent,index_text,words._id " +
-            "FROM pdu,part,addr,words WHERE ((part.mid=pdu._id) AND " +
-            "(addr.msg_id=pdu._id) AND " +
-            "(addr.type=" + PduHeaders.TO + ") AND " +
-            "(part.ct='text/plain') AND " +
-            "(index_text MATCH ?) AND " +
-            "(part._id = words.source_id) AND " +
-            "(words.table_to_use=2))";
+        // Search on the words table but return the rows from the corresponding parts table
+        final String mmsQuery = "SELECT "
+                + pduTable + "._id,"
+                + "thread_id,"
+                + "addr.address,"
+                + "part.text AS body,"
+                + pduTable + ".date,"
+                + pduTable + ".date_sent,"
+                + "index_text,"
+                + "words._id "
+                + "FROM " + pduTable + ",part,addr,words "
+                + "WHERE ((part.mid=" + pduTable + "._id) "
+                + "AND (addr.msg_id=" + pduTable + "._id) "
+                + "AND (addr.type=" + PduHeaders.TO + ") "
+                + "AND (part.ct='text/plain') "
+                + "AND (index_text MATCH ?) "
+                + "AND (part._id = words.source_id) "
+                + "AND (words.table_to_use=2))";
 
-    // This code queries the sms and mms tables and returns a unified result set
-    // of text matches.  We query the sms table which is pretty simple.  We also
-    // query the pdu, part and addr table to get the mms result.  Notet we're
-    // using a UNION so we have to have the same number of result columns from
-    // both queries.
-    private static final String SMS_MMS_QUERY =
-            SMS_QUERY + " UNION " + MMS_QUERY +
-            " GROUP BY thread_id ORDER BY thread_id ASC, date DESC";
+        // This code queries the sms and mms tables and returns a unified result set
+        // of text matches.  We query the sms table which is pretty simple.  We also
+        // query the pdu, part and addr table to get the mms result.  Note we're
+        // using a UNION so we have to have the same number of result columns from
+        // both queries.
+        return smsQuery + " UNION " + mmsQuery + " "
+                + "GROUP BY thread_id "
+                + "ORDER BY thread_id ASC, date DESC";
+    }
 
     private static final String AUTHORITY = "mms-sms";
 
@@ -297,11 +315,22 @@
     @Override
     public Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
+        // First check if restricted views of the "sms" and "pdu" tables should be used based on the
+        // caller's identity. Only system, phone or the default sms app can have full access
+        // of sms/mms data. For other apps, we present a restricted view which only contains sent
+        // or received messages, without wap pushes.
+        final boolean accessRestricted = ProviderUtil.isAccessRestricted(
+                getContext(), getCallingPackage(), Binder.getCallingUid());
+        final String pduTable = MmsProvider.getPduTable(accessRestricted);
+        final String smsTable = SmsProvider.getSmsTable(accessRestricted);
+
         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
         Cursor cursor = null;
-        switch(URI_MATCHER.match(uri)) {
+        final int match = URI_MATCHER.match(uri);
+        switch (match) {
             case URI_COMPLETE_CONVERSATIONS:
-                cursor = getCompleteConversations(projection, selection, sortOrder);
+                cursor = getCompleteConversations(projection, selection, sortOrder, smsTable,
+                        pduTable);
                 break;
             case URI_CONVERSATIONS:
                 String simple = uri.getQueryParameter("simple");
@@ -315,12 +344,12 @@
                             projection, selection, selectionArgs, sortOrder);
                 } else {
                     cursor = getConversations(
-                            projection, selection, sortOrder);
+                            projection, selection, sortOrder, smsTable, pduTable);
                 }
                 break;
             case URI_CONVERSATIONS_MESSAGES:
                 cursor = getConversationMessages(uri.getPathSegments().get(1), projection,
-                        selection, sortOrder);
+                        selection, sortOrder, smsTable, pduTable);
                 break;
             case URI_CONVERSATIONS_RECIPIENTS:
                 cursor = getConversationById(
@@ -334,7 +363,8 @@
                 break;
             case URI_MESSAGES_BY_PHONE:
                 cursor = getMessagesByPhoneNumber(
-                        uri.getPathSegments().get(2), projection, selection, sortOrder);
+                        uri.getPathSegments().get(2), projection, selection, sortOrder, smsTable,
+                        pduTable);
                 break;
             case URI_THREAD_ID:
                 List<String> recipients = uri.getQueryParameters("recipient");
@@ -387,7 +417,7 @@
                     switch (Integer.parseInt(uri.getQueryParameter("table_to_use"))) {
                         case 1:  // sms
                             cursor = db.query(
-                                "sms",
+                                smsTable,
                                 new String[] { "thread_id" },
                                 "_id=?",
                                 new String[] { String.valueOf(id) },
@@ -396,9 +426,10 @@
                                 null);
                             break;
                         case 2:  // mms
-                            String mmsQuery =
-                                "SELECT thread_id FROM pdu,part WHERE ((part.mid=pdu._id) AND " +
-                                "(part._id=?))";
+                            String mmsQuery = "SELECT thread_id "
+                                    + "FROM " + pduTable + ",part "
+                                    + "WHERE ((part.mid=" + pduTable + "._id) "
+                                    + "AND " + "(part._id=?))";
                             cursor = db.rawQuery(mmsQuery, new String[] { String.valueOf(id) });
                             break;
                     }
@@ -420,7 +451,8 @@
                 String searchString = uri.getQueryParameter("pattern") + "*";
 
                 try {
-                    cursor = db.rawQuery(SMS_MMS_QUERY, new String[] { searchString, searchString });
+                    cursor = db.rawQuery(getTextSearchQuery(smsTable, pduTable),
+                            new String[] { searchString, searchString });
                 } catch (Exception ex) {
                     Log.e(LOG_TAG, "got exception: " + ex.toString());
                 }
@@ -448,11 +480,11 @@
             }
             case URI_UNDELIVERED_MSG: {
                 cursor = getUndeliveredMessages(projection, selection,
-                        selectionArgs, sortOrder);
+                        selectionArgs, sortOrder, smsTable, pduTable);
                 break;
             }
             case URI_DRAFT: {
-                cursor = getDraftThread(projection, selection, sortOrder);
+                cursor = getDraftThread(projection, selection, sortOrder, smsTable, pduTable);
                 break;
             }
             case URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID: {
@@ -464,11 +496,12 @@
                     break;
                 }
                 cursor = getFirstLockedMessage(projection, "thread_id=" + Long.toString(threadId),
-                        sortOrder);
+                        sortOrder, smsTable, pduTable);
                 break;
             }
             case URI_FIRST_LOCKED_MESSAGE_ALL: {
-                cursor = getFirstLockedMessage(projection, selection, sortOrder);
+                cursor = getFirstLockedMessage(
+                        projection, selection, sortOrder, smsTable, pduTable);
                 break;
             }
             default:
@@ -743,13 +776,13 @@
      *   ;
      */
     private Cursor getDraftThread(String[] projection, String selection,
-            String sortOrder) {
+            String sortOrder, String smsTable, String pduTable) {
         String[] innerProjection = new String[] {BaseColumns._ID, Conversations.THREAD_ID};
         SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
         SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
 
-        mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU);
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        mmsQueryBuilder.setTables(pduTable);
+        smsQueryBuilder.setTables(smsTable);
 
         String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
                 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
@@ -803,12 +836,12 @@
      * messages.
      */
     private Cursor getConversations(String[] projection, String selection,
-            String sortOrder) {
+            String sortOrder, String smsTable, String pduTable) {
         SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
         SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
 
-        mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU);
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        mmsQueryBuilder.setTables(pduTable);
+        smsQueryBuilder.setTables(smsTable);
 
         String[] columns = handleNullMessageProjection(projection);
         String[] innerMmsProjection = makeProjectionWithDateAndThreadId(
@@ -856,12 +889,12 @@
      * there is *any* locked message, not the actual messages themselves.
      */
     private Cursor getFirstLockedMessage(String[] projection, String selection,
-            String sortOrder) {
+            String sortOrder, String smsTable, String pduTable) {
         SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
         SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
 
-        mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU);
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        mmsQueryBuilder.setTables(pduTable);
+        smsQueryBuilder.setTables(smsTable);
 
         String[] idColumn = new String[] { BaseColumns._ID };
 
@@ -899,8 +932,9 @@
      * and SMS.
      */
     private Cursor getCompleteConversations(String[] projection,
-            String selection, String sortOrder) {
-        String unionQuery = buildConversationQuery(projection, selection, sortOrder);
+            String selection, String sortOrder, String smsTable, String pduTable) {
+        String unionQuery = buildConversationQuery(projection, selection, sortOrder, smsTable,
+                pduTable);
 
         return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
     }
@@ -929,7 +963,7 @@
      */
     private Cursor getConversationMessages(
             String threadIdString, String[] projection, String selection,
-            String sortOrder) {
+            String sortOrder, String smsTable, String pduTable) {
         try {
             Long.parseLong(threadIdString);
         } catch (NumberFormatException exception) {
@@ -939,7 +973,8 @@
 
         String finalSelection = concatSelections(
                 selection, "thread_id = " + threadIdString);
-        String unionQuery = buildConversationQuery(projection, finalSelection, sortOrder);
+        String unionQuery = buildConversationQuery(projection, finalSelection, sortOrder, smsTable,
+                pduTable);
 
         return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
     }
@@ -964,12 +999,12 @@
      */
     private Cursor getMessagesByPhoneNumber(
             String phoneNumber, String[] projection, String selection,
-            String sortOrder) {
+            String sortOrder, String smsTable, String pduTable) {
         String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(phoneNumber);
         String finalMmsSelection =
                 concatSelections(
                         selection,
-                        "pdu._id = matching_addresses.address_msg_id");
+                        pduTable + "._id = matching_addresses.address_msg_id");
         String finalSmsSelection =
                 concatSelections(
                         selection,
@@ -982,14 +1017,14 @@
         mmsQueryBuilder.setDistinct(true);
         smsQueryBuilder.setDistinct(true);
         mmsQueryBuilder.setTables(
-                MmsProvider.TABLE_PDU +
+                pduTable +
                 ", (SELECT msg_id AS address_msg_id " +
                 "FROM addr WHERE (address=" + escapedPhoneNumber +
                 " OR PHONE_NUMBERS_EQUAL(addr.address, " +
                 escapedPhoneNumber +
                 (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0))) ") +
                 "AS matching_addresses");
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        smsQueryBuilder.setTables(smsTable);
 
         String[] columns = handleNullMessageProjection(projection);
         String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
@@ -1033,16 +1068,16 @@
                 selectionArgs, sortOrder, null, null);
     }
 
-    private static String joinPduAndPendingMsgTables() {
-        return MmsProvider.TABLE_PDU + " LEFT JOIN " + TABLE_PENDING_MSG
-                + " ON pdu._id = pending_msgs.msg_id";
+    private static String joinPduAndPendingMsgTables(String pduTable) {
+        return pduTable + " LEFT JOIN " + TABLE_PENDING_MSG
+                + " ON " + pduTable + "._id = pending_msgs.msg_id";
     }
 
-    private static String[] createMmsProjection(String[] old) {
+    private static String[] createMmsProjection(String[] old, String pduTable) {
         String[] newProjection = new String[old.length];
         for (int i = 0; i < old.length; i++) {
             if (old[i].equals(BaseColumns._ID)) {
-                newProjection[i] = "pdu._id";
+                newProjection[i] = pduTable + "._id";
             } else {
                 newProjection[i] = old[i];
             }
@@ -1052,14 +1087,14 @@
 
     private Cursor getUndeliveredMessages(
             String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        String[] mmsProjection = createMmsProjection(projection);
+            String sortOrder, String smsTable, String pduTable) {
+        String[] mmsProjection = createMmsProjection(projection, pduTable);
 
         SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
         SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
 
-        mmsQueryBuilder.setTables(joinPduAndPendingMsgTables());
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable));
+        smsQueryBuilder.setTables(smsTable);
 
         String finalMmsSelection = concatSelections(
                 selection, Mms.MESSAGE_BOX + " = " + Mms.MESSAGE_BOX_OUTBOX);
@@ -1076,7 +1111,7 @@
                 smsColumns, 1);
 
         Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
-        columnsPresentInTable.add("pdu._id");
+        columnsPresentInTable.add(pduTable + "._id");
         columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
         String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
                 MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
@@ -1118,16 +1153,16 @@
     }
 
     private static String buildConversationQuery(String[] projection,
-            String selection, String sortOrder) {
-        String[] mmsProjection = createMmsProjection(projection);
+            String selection, String sortOrder, String smsTable, String pduTable) {
+        String[] mmsProjection = createMmsProjection(projection, pduTable);
 
         SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
         SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
 
         mmsQueryBuilder.setDistinct(true);
         smsQueryBuilder.setDistinct(true);
-        mmsQueryBuilder.setTables(joinPduAndPendingMsgTables());
-        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+        mmsQueryBuilder.setTables(joinPduAndPendingMsgTables(pduTable));
+        smsQueryBuilder.setTables(smsTable);
 
         String[] smsColumns = handleNullMessageProjection(projection);
         String[] mmsColumns = handleNullMessageProjection(mmsProjection);
@@ -1135,7 +1170,7 @@
         String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1);
 
         Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
-        columnsPresentInTable.add("pdu._id");
+        columnsPresentInTable.add(pduTable + "._id");
         columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
 
         String mmsSelection = concatSelections(selection,
@@ -1241,13 +1276,14 @@
     public int update(Uri uri, ContentValues values,
             String selection, String[] selectionArgs) {
         final int callerUid = Binder.getCallingUid();
+        final String callerPkg = getCallingPackage();
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         int affectedRows = 0;
         switch(URI_MATCHER.match(uri)) {
             case URI_CONVERSATIONS_MESSAGES:
                 String threadIdString = uri.getPathSegments().get(1);
                 affectedRows = updateConversation(threadIdString, values,
-                        selection, selectionArgs, callerUid);
+                        selection, selectionArgs, callerUid, callerPkg);
                 break;
 
             case URI_PENDING_MSG:
@@ -1285,9 +1321,8 @@
         return affectedRows;
     }
 
-    private int updateConversation(
-            String threadIdString, ContentValues values, String selection,
-            String[] selectionArgs, int callerUid) {
+    private int updateConversation(String threadIdString, ContentValues values, String selection,
+            String[] selectionArgs, int callerUid, String callerPkg) {
         try {
             Long.parseLong(threadIdString);
         } catch (NumberFormatException exception) {
@@ -1297,8 +1332,7 @@
         }
         if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
             // CREATOR should not be changed by non-SYSTEM/PHONE apps
-            Log.w(LOG_TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
-                    " tries to update CREATOR");
+            Log.w(LOG_TAG, callerPkg + " tries to update CREATOR");
             // Sms.CREATOR and Mms.CREATOR are same. But let's do this
             // twice in case the names may differ in the future
             values.remove(Sms.CREATOR);
diff --git a/src/com/android/providers/telephony/ProviderUtil.java b/src/com/android/providers/telephony/ProviderUtil.java
index 8abe934..9435409 100644
--- a/src/com/android/providers/telephony/ProviderUtil.java
+++ b/src/com/android/providers/telephony/ProviderUtil.java
@@ -18,10 +18,10 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Process;
 import android.provider.Telephony;
-import android.text.TextUtils;
+
+import com.android.internal.telephony.SmsApplication;
 
 /**
  * Helpers
@@ -29,28 +29,18 @@
 public class ProviderUtil {
 
     /**
-     * Get space separated package names associated with a UID
+     * Check if a caller of the provider has restricted access,
+     * i.e. being non-system, non-phone, non-default SMS app
      *
-     * @param context The context to use
-     * @param uid The UID to look up
-     * @return The space separated list of package names for UID
+     * @param context the context to use
+     * @param packageName the caller package name
+     * @param uid the caller uid
+     * @return true if the caller is not system, or phone or default sms app, false otherwise
      */
-    public static String getPackageNamesByUid(Context context, int uid) {
-        final PackageManager pm = context.getPackageManager();
-        final String[] packageNames = pm.getPackagesForUid(uid);
-        if (packageNames != null) {
-            final StringBuilder sb = new StringBuilder();
-            for (String name : packageNames) {
-                if (!TextUtils.isEmpty(name)) {
-                    if (sb.length() > 0) {
-                        sb.append(' ');
-                    }
-                    sb.append(name);
-                }
-            }
-            return sb.toString();
-        }
-        return null;
+    public static boolean isAccessRestricted(Context context, String packageName, int uid) {
+        return (uid != Process.SYSTEM_UID
+                && uid != Process.PHONE_UID
+                && !SmsApplication.isDefaultSmsApplication(context, packageName));
     }
 
     /**
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index 03d2aa6..d48f1c6 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -51,6 +51,7 @@
     static final String TABLE_RAW = "raw";
     private static final String TABLE_SR_PENDING = "sr_pending";
     private static final String TABLE_WORDS = "words";
+    static final String VIEW_SMS_RESTRICTED = "sms_restricted";
 
     private static final Integer ONE = Integer.valueOf(1);
 
@@ -88,48 +89,65 @@
         return true;
     }
 
+    /**
+     * Return the proper view of "sms" table for the current access status.
+     *
+     * @param accessRestricted If the access is restricted
+     * @return the table/view name of the "sms" data
+     */
+    public static String getSmsTable(boolean accessRestricted) {
+        return accessRestricted ? VIEW_SMS_RESTRICTED : TABLE_SMS;
+    }
+
     @Override
     public Cursor query(Uri url, String[] projectionIn, String selection,
             String[] selectionArgs, String sort) {
+        // First check if a restricted view of the "sms" table should be used based on the
+        // caller's identity. Only system, phone or the default sms app can have full access
+        // of sms data. For other apps, we present a restricted view which only contains sent
+        // or received messages.
+        final boolean accessRestricted = ProviderUtil.isAccessRestricted(
+                getContext(), getCallingPackage(), Binder.getCallingUid());
+        final String smsTable = getSmsTable(accessRestricted);
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 
         // Generate the body of the query.
         int match = sURLMatcher.match(url);
         switch (match) {
             case SMS_ALL:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable);
                 break;
 
             case SMS_UNDELIVERED:
-                constructQueryForUndelivered(qb);
+                constructQueryForUndelivered(qb, smsTable);
                 break;
 
             case SMS_FAILED:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED, smsTable);
                 break;
 
             case SMS_QUEUED:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED, smsTable);
                 break;
 
             case SMS_INBOX:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX, smsTable);
                 break;
 
             case SMS_SENT:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT, smsTable);
                 break;
 
             case SMS_DRAFT:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT, smsTable);
                 break;
 
             case SMS_OUTBOX:
-                constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX);
+                constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX, smsTable);
                 break;
 
             case SMS_ALL_ID:
-                qb.setTables(TABLE_SMS);
+                qb.setTables(smsTable);
                 qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")");
                 break;
 
@@ -138,7 +156,7 @@
             case SMS_SENT_ID:
             case SMS_DRAFT_ID:
             case SMS_OUTBOX_ID:
-                qb.setTables(TABLE_SMS);
+                qb.setTables(smsTable);
                 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
                 break;
 
@@ -158,16 +176,28 @@
                     return null;
                 }
 
-                qb.setTables(TABLE_SMS);
+                qb.setTables(smsTable);
                 qb.appendWhere("thread_id = " + threadID);
                 break;
 
             case SMS_CONVERSATIONS:
-                qb.setTables("sms, (SELECT thread_id AS group_thread_id, MAX(date)AS group_date,"
-                       + "COUNT(*) AS msg_count FROM sms GROUP BY thread_id) AS groups");
-                qb.appendWhere("sms.thread_id = groups.group_thread_id AND sms.date ="
-                       + "groups.group_date");
-                qb.setProjectionMap(sConversationProjectionMap);
+                qb.setTables(smsTable + ", "
+                        + "(SELECT thread_id AS group_thread_id, "
+                        + "MAX(date) AS group_date, "
+                        + "COUNT(*) AS msg_count "
+                        + "FROM " + smsTable + " "
+                        + "GROUP BY thread_id) AS groups");
+                qb.appendWhere(smsTable + ".thread_id=groups.group_thread_id"
+                        + " AND " + smsTable + ".date=groups.group_date");
+                final HashMap<String, String> projectionMap = new HashMap<>();
+                projectionMap.put(Sms.Conversations.SNIPPET,
+                        smsTable + ".body AS snippet");
+                projectionMap.put(Sms.Conversations.THREAD_ID,
+                        smsTable + ".thread_id AS thread_id");
+                projectionMap.put(Sms.Conversations.MESSAGE_COUNT,
+                        "groups.msg_count AS msg_count");
+                projectionMap.put("delta", null);
+                qb.setProjectionMap(projectionMap);
                 break;
 
             case SMS_RAW_MESSAGE:
@@ -196,7 +226,7 @@
                 break;
 
             case SMS_STATUS_ID:
-                qb.setTables(TABLE_SMS);
+                qb.setTables(smsTable);
                 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
                 break;
 
@@ -217,7 +247,7 @@
 
         if (!TextUtils.isEmpty(sort)) {
             orderBy = sort;
-        } else if (qb.getTables().equals(TABLE_SMS)) {
+        } else if (qb.getTables().equals(smsTable)) {
             orderBy = Sms.DEFAULT_SORT_ORDER;
         }
 
@@ -314,16 +344,16 @@
         return cursor;
     }
 
-    private void constructQueryForBox(SQLiteQueryBuilder qb, int type) {
-        qb.setTables(TABLE_SMS);
+    private void constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable) {
+        qb.setTables(smsTable);
 
         if (type != Sms.MESSAGE_TYPE_ALL) {
             qb.appendWhere("type=" + type);
         }
     }
 
-    private void constructQueryForUndelivered(SQLiteQueryBuilder qb) {
-        qb.setTables(TABLE_SMS);
+    private void constructQueryForUndelivered(SQLiteQueryBuilder qb, String smsTable) {
+        qb.setTables(smsTable);
 
         qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX +
                        " OR type=" + Sms.MESSAGE_TYPE_FAILED +
@@ -356,15 +386,16 @@
     @Override
     public Uri insert(Uri url, ContentValues initialValues) {
         final int callerUid = Binder.getCallingUid();
+        final String callerPkg = getCallingPackage();
         long token = Binder.clearCallingIdentity();
         try {
-            return insertInner(url, initialValues, callerUid);
+            return insertInner(url, initialValues, callerUid, callerPkg);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private Uri insertInner(Uri url, ContentValues initialValues, int callerUid) {
+    private Uri insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg) {
         ContentValues values;
         long rowID;
         int type = Sms.MESSAGE_TYPE_ALL;
@@ -512,7 +543,7 @@
                 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
                 // set CREATOR using the truth on caller.
                 // Note: Inferring package name from UID may include unrelated package names
-                values.put(Sms.CREATOR, ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
+                values.put(Sms.CREATOR, callerPkg);
             }
         } else {
             if (initialValues == null) {
@@ -643,6 +674,7 @@
     @Override
     public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
         final int callerUid = Binder.getCallingUid();
+        final String callerPkg = getCallingPackage();
         int count = 0;
         String table = TABLE_SMS;
         String extraWhere = null;
@@ -704,8 +736,7 @@
 
         if (table.equals(TABLE_SMS) && ProviderUtil.shouldRemoveCreator(values, callerUid)) {
             // CREATOR should not be changed by non-SYSTEM/PHONE apps
-            Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
-                    " tries to update CREATOR");
+            Log.w(TAG, callerPkg + " tries to update CREATOR");
             values.remove(Sms.CREATOR);
         }
 
@@ -738,8 +769,6 @@
     private final static String VND_ANDROID_DIR_SMS =
             "vnd.android.cursor.dir/sms";
 
-    private static final HashMap<String, String> sConversationProjectionMap =
-            new HashMap<String, String>();
     private static final String[] sIDProjection = new String[] { "_id" };
 
     private static final int SMS_ALL = 0;
@@ -800,13 +829,5 @@
         //we keep these for not breaking old applications
         sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
         sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
-
-        sConversationProjectionMap.put(Sms.Conversations.SNIPPET,
-            "sms.body AS snippet");
-        sConversationProjectionMap.put(Sms.Conversations.THREAD_ID,
-            "sms.thread_id AS thread_id");
-        sConversationProjectionMap.put(Sms.Conversations.MESSAGE_COUNT,
-            "groups.msg_count AS msg_count");
-        sConversationProjectionMap.put("delta", null);
     }
 }