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