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