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