Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.telephony;
     18 
     19 import android.app.AppOpsManager;
     20 import android.content.ContentProvider;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.UriMatcher;
     25 import android.database.Cursor;
     26 import android.database.sqlite.SQLiteDatabase;
     27 import android.database.sqlite.SQLiteException;
     28 import android.database.sqlite.SQLiteOpenHelper;
     29 import android.database.sqlite.SQLiteQueryBuilder;
     30 import android.net.Uri;
     31 import android.os.Binder;
     32 import android.os.FileUtils;
     33 import android.os.ParcelFileDescriptor;
     34 import android.os.UserHandle;
     35 import android.provider.BaseColumns;
     36 import android.provider.Telephony;
     37 import android.provider.Telephony.CanonicalAddressesColumns;
     38 import android.provider.Telephony.Mms;
     39 import android.provider.Telephony.Mms.Addr;
     40 import android.provider.Telephony.Mms.Part;
     41 import android.provider.Telephony.Mms.Rate;
     42 import android.provider.Telephony.MmsSms;
     43 import android.provider.Telephony.Threads;
     44 import android.text.TextUtils;
     45 import android.util.Log;
     46 
     47 import com.google.android.mms.pdu.PduHeaders;
     48 import com.google.android.mms.util.DownloadDrmHelper;
     49 
     50 import java.io.File;
     51 import java.io.FileNotFoundException;
     52 import java.io.IOException;
     53 
     54 /**
     55  * The class to provide base facility to access MMS related content,
     56  * which is stored in a SQLite database and in the file system.
     57  */
     58 public class MmsProvider extends ContentProvider {
     59     static final String TABLE_PDU  = "pdu";
     60     static final String TABLE_ADDR = "addr";
     61     static final String TABLE_PART = "part";
     62     static final String TABLE_RATE = "rate";
     63     static final String TABLE_DRM  = "drm";
     64     static final String TABLE_WORDS = "words";
     65 
     66     // The name of parts directory. The full dir is "app_parts".
     67     private static final String PARTS_DIR_NAME = "parts";
     68 
     69     @Override
     70     public boolean onCreate() {
     71         setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
     72         mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
     73         return true;
     74     }
     75 
     76     @Override
     77     public Cursor query(Uri uri, String[] projection,
     78             String selection, String[] selectionArgs, String sortOrder) {
     79         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
     80 
     81         // Generate the body of the query.
     82         int match = sURLMatcher.match(uri);
     83         if (LOCAL_LOGV) {
     84             Log.v(TAG, "Query uri=" + uri + ", match=" + match);
     85         }
     86 
     87         switch (match) {
     88             case MMS_ALL:
     89                 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL);
     90                 break;
     91             case MMS_INBOX:
     92                 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX);
     93                 break;
     94             case MMS_SENT:
     95                 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT);
     96                 break;
     97             case MMS_DRAFTS:
     98                 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS);
     99                 break;
    100             case MMS_OUTBOX:
    101                 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX);
    102                 break;
    103             case MMS_ALL_ID:
    104                 qb.setTables(TABLE_PDU);
    105                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
    106                 break;
    107             case MMS_INBOX_ID:
    108             case MMS_SENT_ID:
    109             case MMS_DRAFTS_ID:
    110             case MMS_OUTBOX_ID:
    111                 qb.setTables(TABLE_PDU);
    112                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
    113                 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
    114                         + getMessageBoxByMatch(match));
    115                 break;
    116             case MMS_ALL_PART:
    117                 qb.setTables(TABLE_PART);
    118                 break;
    119             case MMS_MSG_PART:
    120                 qb.setTables(TABLE_PART);
    121                 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
    122                 break;
    123             case MMS_PART_ID:
    124                 qb.setTables(TABLE_PART);
    125                 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
    126                 break;
    127             case MMS_MSG_ADDR:
    128                 qb.setTables(TABLE_ADDR);
    129                 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
    130                 break;
    131             case MMS_REPORT_STATUS:
    132                 /*
    133                    SELECT DISTINCT address,
    134                                    T.delivery_status AS delivery_status,
    135                                    T.read_status AS read_status
    136                    FROM addr
    137                    INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
    138                                       ifnull(P2.st, 0) AS delivery_status,
    139                                       ifnull(P3.read_status, 0) AS read_status
    140                                FROM pdu P1
    141                                INNER JOIN pdu P2
    142                                ON P1.m_id = P2.m_id AND P2.m_type = 134
    143                                LEFT JOIN pdu P3
    144                                ON P1.m_id = P3.m_id AND P3.m_type = 136
    145                                UNION
    146                                SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
    147                                       ifnull(P2.st, 0) AS delivery_status,
    148                                       ifnull(P3.read_status, 0) AS read_status
    149                                FROM pdu P1
    150                                INNER JOIN pdu P3
    151                                ON P1.m_id = P3.m_id AND P3.m_type = 136
    152                                LEFT JOIN pdu P2
    153                                ON P1.m_id = P2.m_id AND P2.m_type = 134) T
    154                    ON (msg_id = id2 AND type = 151)
    155                    OR (msg_id = id3 AND type = 137)
    156                    WHERE T.id1 = ?;
    157                  */
    158                 qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" +
    159                              " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" +
    160                              " delivery_status, ifnull(P3.read_status, 0) AS" +
    161                              " read_status FROM pdu P1 INNER JOIN pdu P2 ON" +
    162                              " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" +
    163                              " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" +
    164                              " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" +
    165                              " AS id3, ifnull(P2.st, 0) AS delivery_status," +
    166                              " ifnull(P3.read_status, 0) AS read_status FROM" +
    167                              " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" +
    168                              " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" +
    169                              " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" +
    170                              " OR (msg_id=id3 AND type=137)");
    171                 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
    172                 qb.setDistinct(true);
    173                 break;
    174             case MMS_REPORT_REQUEST:
    175                 /*
    176                    SELECT address, d_rpt, rr
    177                    FROM addr join pdu on pdu._id = addr.msg_id
    178                    WHERE pdu._id = messageId AND addr.type = 151
    179                  */
    180                 qb.setTables(TABLE_ADDR + " join " +
    181                         TABLE_PDU + " on pdu._id = addr.msg_id");
    182                 qb.appendWhere("pdu._id = " + uri.getLastPathSegment());
    183                 qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO);
    184                 break;
    185             case MMS_SENDING_RATE:
    186                 qb.setTables(TABLE_RATE);
    187                 break;
    188             case MMS_DRM_STORAGE_ID:
    189                 qb.setTables(TABLE_DRM);
    190                 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
    191                 break;
    192             case MMS_THREADS:
    193                 qb.setTables("pdu group by thread_id");
    194                 break;
    195             default:
    196                 Log.e(TAG, "query: invalid request: " + uri);
    197                 return null;
    198         }
    199 
    200         String finalSortOrder = null;
    201         if (TextUtils.isEmpty(sortOrder)) {
    202             if (qb.getTables().equals(TABLE_PDU)) {
    203                 finalSortOrder = Mms.DATE + " DESC";
    204             } else if (qb.getTables().equals(TABLE_PART)) {
    205                 finalSortOrder = Part.SEQ;
    206             }
    207         } else {
    208             finalSortOrder = sortOrder;
    209         }
    210 
    211         Cursor ret;
    212         try {
    213             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    214             ret = qb.query(db, projection, selection,
    215                     selectionArgs, null, null, finalSortOrder);
    216         } catch (SQLiteException e) {
    217             Log.e(TAG, "returning NULL cursor, query: " + uri, e);
    218             return null;
    219         }
    220 
    221         // TODO: Does this need to be a URI for this provider.
    222         ret.setNotificationUri(getContext().getContentResolver(), uri);
    223         return ret;
    224     }
    225 
    226     private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) {
    227         qb.setTables(TABLE_PDU);
    228 
    229         if (msgBox != Mms.MESSAGE_BOX_ALL) {
    230             qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
    231         }
    232     }
    233 
    234     @Override
    235     public String getType(Uri uri) {
    236         int match = sURLMatcher.match(uri);
    237         switch (match) {
    238             case MMS_ALL:
    239             case MMS_INBOX:
    240             case MMS_SENT:
    241             case MMS_DRAFTS:
    242             case MMS_OUTBOX:
    243                 return VND_ANDROID_DIR_MMS;
    244             case MMS_ALL_ID:
    245             case MMS_INBOX_ID:
    246             case MMS_SENT_ID:
    247             case MMS_DRAFTS_ID:
    248             case MMS_OUTBOX_ID:
    249                 return VND_ANDROID_MMS;
    250             case MMS_PART_ID: {
    251                 Cursor cursor = mOpenHelper.getReadableDatabase().query(
    252                         TABLE_PART, new String[] { Part.CONTENT_TYPE },
    253                         Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
    254                         null, null, null);
    255                 if (cursor != null) {
    256                     try {
    257                         if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
    258                             return cursor.getString(0);
    259                         } else {
    260                             Log.e(TAG, "cursor.count() != 1: " + uri);
    261                         }
    262                     } finally {
    263                         cursor.close();
    264                     }
    265                 } else {
    266                     Log.e(TAG, "cursor == null: " + uri);
    267                 }
    268                 return "*/*";
    269             }
    270             case MMS_ALL_PART:
    271             case MMS_MSG_PART:
    272             case MMS_MSG_ADDR:
    273             default:
    274                 return "*/*";
    275         }
    276     }
    277 
    278     @Override
    279     public Uri insert(Uri uri, ContentValues values) {
    280         // Don't let anyone insert anything with the _data column
    281         if (values != null && values.containsKey(Part._DATA)) {
    282             return null;
    283         }
    284         final int callerUid = Binder.getCallingUid();
    285         int msgBox = Mms.MESSAGE_BOX_ALL;
    286         boolean notify = true;
    287 
    288         int match = sURLMatcher.match(uri);
    289         if (LOCAL_LOGV) {
    290             Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
    291         }
    292 
    293         String table = TABLE_PDU;
    294         switch (match) {
    295             case MMS_ALL:
    296                 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
    297                 if (msgBoxObj != null) {
    298                     msgBox = (Integer) msgBoxObj;
    299                 }
    300                 else {
    301                     // default to inbox
    302                     msgBox = Mms.MESSAGE_BOX_INBOX;
    303                 }
    304                 break;
    305             case MMS_INBOX:
    306                 msgBox = Mms.MESSAGE_BOX_INBOX;
    307                 break;
    308             case MMS_SENT:
    309                 msgBox = Mms.MESSAGE_BOX_SENT;
    310                 break;
    311             case MMS_DRAFTS:
    312                 msgBox = Mms.MESSAGE_BOX_DRAFTS;
    313                 break;
    314             case MMS_OUTBOX:
    315                 msgBox = Mms.MESSAGE_BOX_OUTBOX;
    316                 break;
    317             case MMS_MSG_PART:
    318                 notify = false;
    319                 table = TABLE_PART;
    320                 break;
    321             case MMS_MSG_ADDR:
    322                 notify = false;
    323                 table = TABLE_ADDR;
    324                 break;
    325             case MMS_SENDING_RATE:
    326                 notify = false;
    327                 table = TABLE_RATE;
    328                 break;
    329             case MMS_DRM_STORAGE:
    330                 notify = false;
    331                 table = TABLE_DRM;
    332                 break;
    333             default:
    334                 Log.e(TAG, "insert: invalid request: " + uri);
    335                 return null;
    336         }
    337 
    338         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    339         ContentValues finalValues;
    340         Uri res = Mms.CONTENT_URI;
    341         long rowId;
    342 
    343         if (table.equals(TABLE_PDU)) {
    344             boolean addDate = !values.containsKey(Mms.DATE);
    345             boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
    346 
    347             // Filter keys we don't support yet.
    348             filterUnsupportedKeys(values);
    349 
    350             // TODO: Should initialValues be validated, e.g. if it
    351             // missed some significant keys?
    352             finalValues = new ContentValues(values);
    353 
    354             long timeInMillis = System.currentTimeMillis();
    355 
    356             if (addDate) {
    357                 finalValues.put(Mms.DATE, timeInMillis / 1000L);
    358             }
    359 
    360             if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
    361                 finalValues.put(Mms.MESSAGE_BOX, msgBox);
    362             }
    363 
    364             if (msgBox != Mms.MESSAGE_BOX_INBOX) {
    365                 // Mark all non-inbox messages read.
    366                 finalValues.put(Mms.READ, 1);
    367             }
    368 
    369             // thread_id
    370             Long threadId = values.getAsLong(Mms.THREAD_ID);
    371             String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
    372 
    373             if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
    374                 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
    375             }
    376 
    377             if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
    378                 // Only SYSTEM or PHONE can set CREATOR
    379                 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
    380                 // set CREATOR using the truth on caller.
    381                 // Note: Inferring package name from UID may include unrelated package names
    382                 finalValues.put(Telephony.Mms.CREATOR,
    383                         ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
    384             }
    385 
    386             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
    387                 Log.e(TAG, "MmsProvider.insert: failed!");
    388                 return null;
    389             }
    390 
    391             res = Uri.parse(res + "/" + rowId);
    392         } else if (table.equals(TABLE_ADDR)) {
    393             finalValues = new ContentValues(values);
    394             finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
    395 
    396             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
    397                 Log.e(TAG, "Failed to insert address");
    398                 return null;
    399             }
    400 
    401             res = Uri.parse(res + "/addr/" + rowId);
    402         } else if (table.equals(TABLE_PART)) {
    403             finalValues = new ContentValues(values);
    404 
    405             if (match == MMS_MSG_PART) {
    406                 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
    407             }
    408 
    409             String contentType = values.getAsString("ct");
    410 
    411             // text/plain and app application/smil store their "data" inline in the
    412             // table so there's no need to create the file
    413             boolean plainText = false;
    414             boolean smilText = false;
    415             if ("text/plain".equals(contentType)) {
    416                 plainText = true;
    417             } else if ("application/smil".equals(contentType)) {
    418                 smilText = true;
    419             }
    420             if (!plainText && !smilText) {
    421                 // Use the filename if possible, otherwise use the current time as the name.
    422                 String contentLocation = values.getAsString("cl");
    423                 if (!TextUtils.isEmpty(contentLocation)) {
    424                     File f = new File(contentLocation);
    425                     contentLocation = "_" + f.getName();
    426                 } else {
    427                     contentLocation = "";
    428                 }
    429 
    430                 // Generate the '_data' field of the part with default
    431                 // permission settings.
    432                 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
    433                         + "/PART_" + System.currentTimeMillis() + contentLocation;
    434 
    435                 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
    436                     // Adds the .fl extension to the filename if contentType is
    437                     // "application/vnd.oma.drm.message"
    438                     path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
    439                 }
    440 
    441                 finalValues.put(Part._DATA, path);
    442 
    443                 File partFile = new File(path);
    444                 if (!partFile.exists()) {
    445                     try {
    446                         if (!partFile.createNewFile()) {
    447                             throw new IllegalStateException(
    448                                     "Unable to create new partFile: " + path);
    449                         }
    450                         // Give everyone rw permission until we encrypt the file
    451                         // (in PduPersister.persistData). Once the file is encrypted, the
    452                         // permissions will be set to 0644.
    453                         int result = FileUtils.setPermissions(path, 0666, -1, -1);
    454                         if (LOCAL_LOGV) {
    455                             Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
    456                         }
    457                     } catch (IOException e) {
    458                         Log.e(TAG, "createNewFile", e);
    459                         throw new IllegalStateException(
    460                                 "Unable to create new partFile: " + path);
    461                     }
    462                 }
    463             }
    464 
    465             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
    466                 Log.e(TAG, "MmsProvider.insert: failed!");
    467                 return null;
    468             }
    469 
    470             res = Uri.parse(res + "/part/" + rowId);
    471 
    472             // Don't use a trigger for updating the words table because of a bug
    473             // in FTS3.  The bug is such that the call to get the last inserted
    474             // row is incorrect.
    475             if (plainText) {
    476                 // Update the words table with a corresponding row.  The words table
    477                 // allows us to search for words quickly, without scanning the whole
    478                 // table;
    479                 ContentValues cv = new ContentValues();
    480 
    481                 // we're using the row id of the part table row but we're also using ids
    482                 // from the sms table so this divides the space into two large chunks.
    483                 // The row ids from the part table start at 2 << 32.
    484                 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
    485                 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
    486                 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
    487                 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
    488                 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
    489             }
    490 
    491         } else if (table.equals(TABLE_RATE)) {
    492             long now = values.getAsLong(Rate.SENT_TIME);
    493             long oneHourAgo = now - 1000 * 60 * 60;
    494             // Delete all unused rows (time earlier than one hour ago).
    495             db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
    496             db.insert(table, null, values);
    497         } else if (table.equals(TABLE_DRM)) {
    498             String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
    499                     + "/PART_" + System.currentTimeMillis();
    500             finalValues = new ContentValues(1);
    501             finalValues.put("_data", path);
    502 
    503             File partFile = new File(path);
    504             if (!partFile.exists()) {
    505                 try {
    506                     if (!partFile.createNewFile()) {
    507                         throw new IllegalStateException(
    508                                 "Unable to create new file: " + path);
    509                     }
    510                 } catch (IOException e) {
    511                     Log.e(TAG, "createNewFile", e);
    512                     throw new IllegalStateException(
    513                             "Unable to create new file: " + path);
    514                 }
    515             }
    516 
    517             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
    518                 Log.e(TAG, "MmsProvider.insert: failed!");
    519                 return null;
    520             }
    521             res = Uri.parse(res + "/drm/" + rowId);
    522         } else {
    523             throw new AssertionError("Unknown table type: " + table);
    524         }
    525 
    526         if (notify) {
    527             notifyChange();
    528         }
    529         return res;
    530     }
    531 
    532     private int getMessageBoxByMatch(int match) {
    533         switch (match) {
    534             case MMS_INBOX_ID:
    535             case MMS_INBOX:
    536                 return Mms.MESSAGE_BOX_INBOX;
    537             case MMS_SENT_ID:
    538             case MMS_SENT:
    539                 return Mms.MESSAGE_BOX_SENT;
    540             case MMS_DRAFTS_ID:
    541             case MMS_DRAFTS:
    542                 return Mms.MESSAGE_BOX_DRAFTS;
    543             case MMS_OUTBOX_ID:
    544             case MMS_OUTBOX:
    545                 return Mms.MESSAGE_BOX_OUTBOX;
    546             default:
    547                 throw new IllegalArgumentException("bad Arg: " + match);
    548         }
    549     }
    550 
    551     @Override
    552     public int delete(Uri uri, String selection,
    553             String[] selectionArgs) {
    554         int match = sURLMatcher.match(uri);
    555         if (LOCAL_LOGV) {
    556             Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
    557         }
    558 
    559         String table, extraSelection = null;
    560         boolean notify = false;
    561 
    562         switch (match) {
    563             case MMS_ALL_ID:
    564             case MMS_INBOX_ID:
    565             case MMS_SENT_ID:
    566             case MMS_DRAFTS_ID:
    567             case MMS_OUTBOX_ID:
    568                 notify = true;
    569                 table = TABLE_PDU;
    570                 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
    571                 break;
    572             case MMS_ALL:
    573             case MMS_INBOX:
    574             case MMS_SENT:
    575             case MMS_DRAFTS:
    576             case MMS_OUTBOX:
    577                 notify = true;
    578                 table = TABLE_PDU;
    579                 if (match != MMS_ALL) {
    580                     int msgBox = getMessageBoxByMatch(match);
    581                     extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
    582                 }
    583                 break;
    584             case MMS_ALL_PART:
    585                 table = TABLE_PART;
    586                 break;
    587             case MMS_MSG_PART:
    588                 table = TABLE_PART;
    589                 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
    590                 break;
    591             case MMS_PART_ID:
    592                 table = TABLE_PART;
    593                 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
    594                 break;
    595             case MMS_MSG_ADDR:
    596                 table = TABLE_ADDR;
    597                 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
    598                 break;
    599             case MMS_DRM_STORAGE:
    600                 table = TABLE_DRM;
    601                 break;
    602             default:
    603                 Log.w(TAG, "No match for URI '" + uri + "'");
    604                 return 0;
    605         }
    606 
    607         String finalSelection = concatSelections(selection, extraSelection);
    608         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    609         int deletedRows = 0;
    610 
    611         if (TABLE_PDU.equals(table)) {
    612             deletedRows = deleteMessages(getContext(), db, finalSelection,
    613                                          selectionArgs, uri);
    614         } else if (TABLE_PART.equals(table)) {
    615             deletedRows = deleteParts(db, finalSelection, selectionArgs);
    616         } else if (TABLE_DRM.equals(table)) {
    617             deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
    618         } else {
    619             deletedRows = db.delete(table, finalSelection, selectionArgs);
    620         }
    621 
    622         if ((deletedRows > 0) && notify) {
    623             notifyChange();
    624         }
    625         return deletedRows;
    626     }
    627 
    628     static int deleteMessages(Context context, SQLiteDatabase db,
    629             String selection, String[] selectionArgs, Uri uri) {
    630         Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
    631                 selection, selectionArgs, null, null, null);
    632         if (cursor == null) {
    633             return 0;
    634         }
    635 
    636         try {
    637             if (cursor.getCount() == 0) {
    638                 return 0;
    639             }
    640 
    641             while (cursor.moveToNext()) {
    642                 deleteParts(db, Part.MSG_ID + " = ?",
    643                         new String[] { String.valueOf(cursor.getLong(0)) });
    644             }
    645         } finally {
    646             cursor.close();
    647         }
    648 
    649         int count = db.delete(TABLE_PDU, selection, selectionArgs);
    650         if (count > 0) {
    651             Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
    652             intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
    653             if (LOCAL_LOGV) {
    654                 Log.v(TAG, "Broadcasting intent: " + intent);
    655             }
    656             context.sendBroadcast(intent);
    657         }
    658         return count;
    659     }
    660 
    661     private static int deleteParts(SQLiteDatabase db, String selection,
    662             String[] selectionArgs) {
    663         return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
    664     }
    665 
    666     private static int deleteTempDrmData(SQLiteDatabase db, String selection,
    667             String[] selectionArgs) {
    668         return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
    669     }
    670 
    671     private static int deleteDataRows(SQLiteDatabase db, String table,
    672             String selection, String[] selectionArgs) {
    673         Cursor cursor = db.query(table, new String[] { "_data" },
    674                 selection, selectionArgs, null, null, null);
    675         if (cursor == null) {
    676             // FIXME: This might be an error, ignore it may cause
    677             // unpredictable result.
    678             return 0;
    679         }
    680 
    681         try {
    682             if (cursor.getCount() == 0) {
    683                 return 0;
    684             }
    685 
    686             while (cursor.moveToNext()) {
    687                 try {
    688                     // Delete the associated files saved on file-system.
    689                     String path = cursor.getString(0);
    690                     if (path != null) {
    691                         new File(path).delete();
    692                     }
    693                 } catch (Throwable ex) {
    694                     Log.e(TAG, ex.getMessage(), ex);
    695                 }
    696             }
    697         } finally {
    698             cursor.close();
    699         }
    700 
    701         return db.delete(table, selection, selectionArgs);
    702     }
    703 
    704     @Override
    705     public int update(Uri uri, ContentValues values,
    706             String selection, String[] selectionArgs) {
    707         // Don't let anyone update the _data column
    708         if (values != null && values.containsKey(Part._DATA)) {
    709             return 0;
    710         }
    711         final int callerUid = Binder.getCallingUid();
    712         int match = sURLMatcher.match(uri);
    713         if (LOCAL_LOGV) {
    714             Log.v(TAG, "Update uri=" + uri + ", match=" + match);
    715         }
    716 
    717         boolean notify = false;
    718         String msgId = null;
    719         String table;
    720 
    721         switch (match) {
    722             case MMS_ALL_ID:
    723             case MMS_INBOX_ID:
    724             case MMS_SENT_ID:
    725             case MMS_DRAFTS_ID:
    726             case MMS_OUTBOX_ID:
    727                 msgId = uri.getLastPathSegment();
    728             // fall-through
    729             case MMS_ALL:
    730             case MMS_INBOX:
    731             case MMS_SENT:
    732             case MMS_DRAFTS:
    733             case MMS_OUTBOX:
    734                 notify = true;
    735                 table = TABLE_PDU;
    736                 break;
    737 
    738             case MMS_MSG_PART:
    739             case MMS_PART_ID:
    740                 table = TABLE_PART;
    741                 break;
    742 
    743             case MMS_PART_RESET_FILE_PERMISSION:
    744                 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
    745                         uri.getPathSegments().get(1);
    746                 // Reset the file permission back to read for everyone but me.
    747                 int result = FileUtils.setPermissions(path, 0644, -1, -1);
    748                 if (LOCAL_LOGV) {
    749                     Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
    750                             " for path: " + path);
    751                 }
    752                 return 0;
    753 
    754             default:
    755                 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
    756                 return 0;
    757         }
    758 
    759         String extraSelection = null;
    760         ContentValues finalValues;
    761         if (table.equals(TABLE_PDU)) {
    762             // Filter keys that we don't support yet.
    763             filterUnsupportedKeys(values);
    764             if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
    765                 // CREATOR should not be changed by non-SYSTEM/PHONE apps
    766                 Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
    767                         " tries to update CREATOR");
    768                 values.remove(Mms.CREATOR);
    769             }
    770             finalValues = new ContentValues(values);
    771 
    772             if (msgId != null) {
    773                 extraSelection = Mms._ID + "=" + msgId;
    774             }
    775         } else if (table.equals(TABLE_PART)) {
    776             finalValues = new ContentValues(values);
    777 
    778             switch (match) {
    779                 case MMS_MSG_PART:
    780                     extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
    781                     break;
    782                 case MMS_PART_ID:
    783                     extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
    784                     break;
    785                 default:
    786                     break;
    787             }
    788         } else {
    789             return 0;
    790         }
    791 
    792         String finalSelection = concatSelections(selection, extraSelection);
    793         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    794         int count = db.update(table, finalValues, finalSelection, selectionArgs);
    795         if (notify && (count > 0)) {
    796             notifyChange();
    797         }
    798         return count;
    799     }
    800 
    801     @Override
    802     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    803         int match = sURLMatcher.match(uri);
    804 
    805         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    806             Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
    807         }
    808 
    809         if (match != MMS_PART_ID) {
    810             return null;
    811         }
    812 
    813         // Verify that the _data path points to mms data
    814         Cursor c = query(uri, new String[]{"_data"}, null, null, null);
    815         int count = (c != null) ? c.getCount() : 0;
    816         if (count != 1) {
    817             // If there is not exactly one result, throw an appropriate
    818             // exception.
    819             if (c != null) {
    820                 c.close();
    821             }
    822             if (count == 0) {
    823                 throw new FileNotFoundException("No entry for " + uri);
    824             }
    825             throw new FileNotFoundException("Multiple items at " + uri);
    826         }
    827 
    828         c.moveToFirst();
    829         int i = c.getColumnIndex("_data");
    830         String path = (i >= 0 ? c.getString(i) : null);
    831         c.close();
    832 
    833         if (path == null) {
    834             return null;
    835         }
    836         try {
    837             File filePath = new File(path);
    838             if (!filePath.getCanonicalPath()
    839                     .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getPath())) {
    840                 Log.e(TAG, "openFile: path "
    841                         + filePath.getCanonicalPath()
    842                         + " does not start with "
    843                         + getContext().getDir(PARTS_DIR_NAME, 0).getPath());
    844                 // Don't care return value
    845                 filePath.delete();
    846                 return null;
    847             }
    848         } catch (IOException e) {
    849             Log.e(TAG, "openFile: create path failed " + e, e);
    850             return null;
    851         }
    852 
    853         return openFileHelper(uri, mode);
    854     }
    855 
    856     private void filterUnsupportedKeys(ContentValues values) {
    857         // Some columns are unsupported.  They should therefore
    858         // neither be inserted nor updated.  Filter them out.
    859         values.remove(Mms.DELIVERY_TIME_TOKEN);
    860         values.remove(Mms.SENDER_VISIBILITY);
    861         values.remove(Mms.REPLY_CHARGING);
    862         values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
    863         values.remove(Mms.REPLY_CHARGING_DEADLINE);
    864         values.remove(Mms.REPLY_CHARGING_ID);
    865         values.remove(Mms.REPLY_CHARGING_SIZE);
    866         values.remove(Mms.PREVIOUSLY_SENT_BY);
    867         values.remove(Mms.PREVIOUSLY_SENT_DATE);
    868         values.remove(Mms.STORE);
    869         values.remove(Mms.MM_STATE);
    870         values.remove(Mms.MM_FLAGS_TOKEN);
    871         values.remove(Mms.MM_FLAGS);
    872         values.remove(Mms.STORE_STATUS);
    873         values.remove(Mms.STORE_STATUS_TEXT);
    874         values.remove(Mms.STORED);
    875         values.remove(Mms.TOTALS);
    876         values.remove(Mms.MBOX_TOTALS);
    877         values.remove(Mms.MBOX_TOTALS_TOKEN);
    878         values.remove(Mms.QUOTAS);
    879         values.remove(Mms.MBOX_QUOTAS);
    880         values.remove(Mms.MBOX_QUOTAS_TOKEN);
    881         values.remove(Mms.MESSAGE_COUNT);
    882         values.remove(Mms.START);
    883         values.remove(Mms.DISTRIBUTION_INDICATOR);
    884         values.remove(Mms.ELEMENT_DESCRIPTOR);
    885         values.remove(Mms.LIMIT);
    886         values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
    887         values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
    888         values.remove(Mms.STATUS_TEXT);
    889         values.remove(Mms.APPLIC_ID);
    890         values.remove(Mms.REPLY_APPLIC_ID);
    891         values.remove(Mms.AUX_APPLIC_ID);
    892         values.remove(Mms.DRM_CONTENT);
    893         values.remove(Mms.ADAPTATION_ALLOWED);
    894         values.remove(Mms.REPLACE_ID);
    895         values.remove(Mms.CANCEL_ID);
    896         values.remove(Mms.CANCEL_STATUS);
    897 
    898         // Keys shouldn't be inserted or updated.
    899         values.remove(Mms._ID);
    900     }
    901 
    902     private void notifyChange() {
    903         getContext().getContentResolver().notifyChange(
    904                 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
    905     }
    906 
    907     private final static String TAG = "MmsProvider";
    908     private final static String VND_ANDROID_MMS = "vnd.android/mms";
    909     private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
    910     private final static boolean DEBUG = false;
    911     private final static boolean LOCAL_LOGV = false;
    912 
    913     private static final int MMS_ALL                      = 0;
    914     private static final int MMS_ALL_ID                   = 1;
    915     private static final int MMS_INBOX                    = 2;
    916     private static final int MMS_INBOX_ID                 = 3;
    917     private static final int MMS_SENT                     = 4;
    918     private static final int MMS_SENT_ID                  = 5;
    919     private static final int MMS_DRAFTS                   = 6;
    920     private static final int MMS_DRAFTS_ID                = 7;
    921     private static final int MMS_OUTBOX                   = 8;
    922     private static final int MMS_OUTBOX_ID                = 9;
    923     private static final int MMS_ALL_PART                 = 10;
    924     private static final int MMS_MSG_PART                 = 11;
    925     private static final int MMS_PART_ID                  = 12;
    926     private static final int MMS_MSG_ADDR                 = 13;
    927     private static final int MMS_SENDING_RATE             = 14;
    928     private static final int MMS_REPORT_STATUS            = 15;
    929     private static final int MMS_REPORT_REQUEST           = 16;
    930     private static final int MMS_DRM_STORAGE              = 17;
    931     private static final int MMS_DRM_STORAGE_ID           = 18;
    932     private static final int MMS_THREADS                  = 19;
    933     private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
    934 
    935     private static final UriMatcher
    936             sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    937 
    938     static {
    939         sURLMatcher.addURI("mms", null,         MMS_ALL);
    940         sURLMatcher.addURI("mms", "#",          MMS_ALL_ID);
    941         sURLMatcher.addURI("mms", "inbox",      MMS_INBOX);
    942         sURLMatcher.addURI("mms", "inbox/#",    MMS_INBOX_ID);
    943         sURLMatcher.addURI("mms", "sent",       MMS_SENT);
    944         sURLMatcher.addURI("mms", "sent/#",     MMS_SENT_ID);
    945         sURLMatcher.addURI("mms", "drafts",     MMS_DRAFTS);
    946         sURLMatcher.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
    947         sURLMatcher.addURI("mms", "outbox",     MMS_OUTBOX);
    948         sURLMatcher.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
    949         sURLMatcher.addURI("mms", "part",       MMS_ALL_PART);
    950         sURLMatcher.addURI("mms", "#/part",     MMS_MSG_PART);
    951         sURLMatcher.addURI("mms", "part/#",     MMS_PART_ID);
    952         sURLMatcher.addURI("mms", "#/addr",     MMS_MSG_ADDR);
    953         sURLMatcher.addURI("mms", "rate",       MMS_SENDING_RATE);
    954         sURLMatcher.addURI("mms", "report-status/#",  MMS_REPORT_STATUS);
    955         sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
    956         sURLMatcher.addURI("mms", "drm",        MMS_DRM_STORAGE);
    957         sURLMatcher.addURI("mms", "drm/#",      MMS_DRM_STORAGE_ID);
    958         sURLMatcher.addURI("mms", "threads",    MMS_THREADS);
    959         sURLMatcher.addURI("mms", "resetFilePerm/*",    MMS_PART_RESET_FILE_PERMISSION);
    960     }
    961 
    962     private SQLiteOpenHelper mOpenHelper;
    963 
    964     private static String concatSelections(String selection1, String selection2) {
    965         if (TextUtils.isEmpty(selection1)) {
    966             return selection2;
    967         } else if (TextUtils.isEmpty(selection2)) {
    968             return selection1;
    969         } else {
    970             return selection1 + " AND " + selection2;
    971         }
    972     }
    973 }
    974 
    975