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