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