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