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