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
17package com.android.providers.telephony;
18
19import android.content.ContentProvider;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.UriMatcher;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteOpenHelper;
27import android.database.sqlite.SQLiteQueryBuilder;
28import android.net.Uri;
29import android.os.FileUtils;
30import android.os.ParcelFileDescriptor;
31import android.provider.BaseColumns;
32import android.provider.Telephony;
33import android.provider.Telephony.CanonicalAddressesColumns;
34import android.provider.Telephony.Mms;
35import android.provider.Telephony.MmsSms;
36import android.provider.Telephony.Mms.Addr;
37import android.provider.Telephony.Mms.Part;
38import android.provider.Telephony.Mms.Rate;
39import android.text.TextUtils;
40import android.util.Log;
41
42
43import com.google.android.mms.pdu.PduHeaders;
44import com.google.android.mms.util.DownloadDrmHelper;
45
46import java.io.File;
47import java.io.FileNotFoundException;
48import java.io.IOException;
49import 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 */
55public 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