blob: 66cd167d9e3f74e933fb6daf2b7efad2a341a41c [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;
Ye Wen496e4b62014-11-19 12:06:05 -080031import android.os.Binder;
Tom Taylorc2db47d2012-03-27 15:15:39 -070032import android.os.FileUtils;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080033import android.os.ParcelFileDescriptor;
Amith Yamasani43f9fb22014-09-10 15:56:47 -070034import android.os.UserHandle;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080035import android.provider.BaseColumns;
Mark Wagner8e5ee782010-01-04 17:39:06 -080036import android.provider.Telephony;
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -070037import android.provider.Telephony.CanonicalAddressesColumns;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080038import android.provider.Telephony.Mms;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080039import android.provider.Telephony.Mms.Addr;
40import android.provider.Telephony.Mms.Part;
41import android.provider.Telephony.Mms.Rate;
Ye Wen496e4b62014-11-19 12:06:05 -080042import android.provider.Telephony.MmsSms;
43import android.provider.Telephony.Threads;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080044import android.text.TextUtils;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080045import android.util.Log;
46
Tom Taylorb1bae652010-03-08 16:33:42 -080047import com.google.android.mms.pdu.PduHeaders;
Tom Taylorc2db47d2012-03-27 15:15:39 -070048import com.google.android.mms.util.DownloadDrmHelper;
Tom Taylorc71e7702010-01-28 09:23:12 -080049
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080050import java.io.File;
51import java.io.FileNotFoundException;
52import java.io.IOException;
Amith Yamasani43f9fb22014-09-10 15:56:47 -070053
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080054/**
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() {
Kun Liange3e884d2013-10-02 10:26:25 +080069 setAppOps(AppOpsManager.OP_READ_MMS, AppOpsManager.OP_WRITE_MMS);
Ye Wenb2ce2d32014-07-28 14:49:30 -070070 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 }
Ye Wen496e4b62014-11-19 12:06:05 -0800282 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800283 int msgBox = Mms.MESSAGE_BOX_ALL;
284 boolean notify = true;
285
286 int match = sURLMatcher.match(uri);
287 if (LOCAL_LOGV) {
288 Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
289 }
290
291 String table = TABLE_PDU;
292 switch (match) {
293 case MMS_ALL:
294 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
295 if (msgBoxObj != null) {
296 msgBox = (Integer) msgBoxObj;
297 }
298 else {
299 // default to inbox
300 msgBox = Mms.MESSAGE_BOX_INBOX;
301 }
302 break;
303 case MMS_INBOX:
304 msgBox = Mms.MESSAGE_BOX_INBOX;
305 break;
306 case MMS_SENT:
307 msgBox = Mms.MESSAGE_BOX_SENT;
308 break;
309 case MMS_DRAFTS:
310 msgBox = Mms.MESSAGE_BOX_DRAFTS;
311 break;
312 case MMS_OUTBOX:
313 msgBox = Mms.MESSAGE_BOX_OUTBOX;
314 break;
315 case MMS_MSG_PART:
316 notify = false;
317 table = TABLE_PART;
318 break;
319 case MMS_MSG_ADDR:
320 notify = false;
321 table = TABLE_ADDR;
322 break;
323 case MMS_SENDING_RATE:
324 notify = false;
325 table = TABLE_RATE;
326 break;
327 case MMS_DRM_STORAGE:
328 notify = false;
329 table = TABLE_DRM;
330 break;
331 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700332 Log.e(TAG, "insert: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800333 return null;
334 }
335
336 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
337 ContentValues finalValues;
338 Uri res = Mms.CONTENT_URI;
339 long rowId;
340
341 if (table.equals(TABLE_PDU)) {
342 boolean addDate = !values.containsKey(Mms.DATE);
343 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
344
345 // Filter keys we don't support yet.
346 filterUnsupportedKeys(values);
347
348 // TODO: Should initialValues be validated, e.g. if it
349 // missed some significant keys?
350 finalValues = new ContentValues(values);
351
352 long timeInMillis = System.currentTimeMillis();
353
354 if (addDate) {
355 finalValues.put(Mms.DATE, timeInMillis / 1000L);
356 }
357
358 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
359 finalValues.put(Mms.MESSAGE_BOX, msgBox);
360 }
361
362 if (msgBox != Mms.MESSAGE_BOX_INBOX) {
363 // Mark all non-inbox messages read.
364 finalValues.put(Mms.READ, 1);
365 }
366
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700367 // thread_id
368 Long threadId = values.getAsLong(Mms.THREAD_ID);
369 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
370
Tom Taylor59269962012-05-09 14:42:35 -0700371 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700372 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
373 }
374
Ye Wen496e4b62014-11-19 12:06:05 -0800375 if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
376 // Only SYSTEM or PHONE can set CREATOR
377 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
378 // set CREATOR using the truth on caller.
379 // Note: Inferring package name from UID may include unrelated package names
380 finalValues.put(Telephony.Mms.CREATOR,
381 ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
382 }
383
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800384 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800385 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800386 return null;
387 }
388
389 res = Uri.parse(res + "/" + rowId);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800390 } else if (table.equals(TABLE_ADDR)) {
391 finalValues = new ContentValues(values);
392 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
393
394 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800395 Log.e(TAG, "Failed to insert address");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800396 return null;
397 }
398
399 res = Uri.parse(res + "/addr/" + rowId);
400 } else if (table.equals(TABLE_PART)) {
401 finalValues = new ContentValues(values);
402
403 if (match == MMS_MSG_PART) {
404 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
405 }
406
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700407 String contentType = values.getAsString("ct");
Mark Wagner8e5ee782010-01-04 17:39:06 -0800408
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700409 // text/plain and app application/smil store their "data" inline in the
410 // table so there's no need to create the file
Tom Taylorc2db47d2012-03-27 15:15:39 -0700411 boolean plainText = false;
412 boolean smilText = false;
413 if ("text/plain".equals(contentType)) {
414 plainText = true;
415 } else if ("application/smil".equals(contentType)) {
416 smilText = true;
417 }
Mark Wagner8e5ee782010-01-04 17:39:06 -0800418 if (!plainText && !smilText) {
Tom Taylorc2db47d2012-03-27 15:15:39 -0700419 // Use the filename if possible, otherwise use the current time as the name.
420 String contentLocation = values.getAsString("cl");
421 if (!TextUtils.isEmpty(contentLocation)) {
422 File f = new File(contentLocation);
423 contentLocation = "_" + f.getName();
424 } else {
425 contentLocation = "";
426 }
427
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700428 // Generate the '_data' field of the part with default
429 // permission settings.
430 String path = getContext().getDir("parts", 0).getPath()
Tom Taylorc2db47d2012-03-27 15:15:39 -0700431 + "/PART_" + System.currentTimeMillis() + contentLocation;
432
433 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
434 // Adds the .fl extension to the filename if contentType is
435 // "application/vnd.oma.drm.message"
436 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
437 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800438
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700439 finalValues.put(Part._DATA, path);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800440
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700441 File partFile = new File(path);
442 if (!partFile.exists()) {
443 try {
444 if (!partFile.createNewFile()) {
445 throw new IllegalStateException(
446 "Unable to create new partFile: " + path);
447 }
Tom Taylorc2db47d2012-03-27 15:15:39 -0700448 // Give everyone rw permission until we encrypt the file
449 // (in PduPersister.persistData). Once the file is encrypted, the
450 // permissions will be set to 0644.
451 int result = FileUtils.setPermissions(path, 0666, -1, -1);
452 if (LOCAL_LOGV) {
453 Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
454 }
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700455 } catch (IOException e) {
456 Log.e(TAG, "createNewFile", e);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800457 throw new IllegalStateException(
458 "Unable to create new partFile: " + path);
459 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800460 }
461 }
462
463 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800464 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800465 return null;
466 }
467
468 res = Uri.parse(res + "/part/" + rowId);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800469
470 // Don't use a trigger for updating the words table because of a bug
471 // in FTS3. The bug is such that the call to get the last inserted
472 // row is incorrect.
473 if (plainText) {
474 // Update the words table with a corresponding row. The words table
475 // allows us to search for words quickly, without scanning the whole
476 // table;
477 ContentValues cv = new ContentValues();
478
479 // we're using the row id of the part table row but we're also using ids
480 // from the sms table so this divides the space into two large chunks.
481 // The row ids from the part table start at 2 << 32.
482 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
483 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
484 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
485 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
486 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
487 }
488
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800489 } else if (table.equals(TABLE_RATE)) {
490 long now = values.getAsLong(Rate.SENT_TIME);
491 long oneHourAgo = now - 1000 * 60 * 60;
492 // Delete all unused rows (time earlier than one hour ago).
493 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
494 db.insert(table, null, values);
495 } else if (table.equals(TABLE_DRM)) {
496 String path = getContext().getDir("parts", 0).getPath()
497 + "/PART_" + System.currentTimeMillis();
498 finalValues = new ContentValues(1);
499 finalValues.put("_data", path);
500
501 File partFile = new File(path);
502 if (!partFile.exists()) {
503 try {
504 if (!partFile.createNewFile()) {
505 throw new IllegalStateException(
506 "Unable to create new file: " + path);
507 }
508 } catch (IOException e) {
509 Log.e(TAG, "createNewFile", e);
510 throw new IllegalStateException(
511 "Unable to create new file: " + path);
512 }
513 }
514
515 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800516 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800517 return null;
518 }
519 res = Uri.parse(res + "/drm/" + rowId);
520 } else {
521 throw new AssertionError("Unknown table type: " + table);
522 }
523
524 if (notify) {
525 notifyChange();
526 }
527 return res;
528 }
529
530 private int getMessageBoxByMatch(int match) {
531 switch (match) {
532 case MMS_INBOX_ID:
533 case MMS_INBOX:
534 return Mms.MESSAGE_BOX_INBOX;
535 case MMS_SENT_ID:
536 case MMS_SENT:
537 return Mms.MESSAGE_BOX_SENT;
538 case MMS_DRAFTS_ID:
539 case MMS_DRAFTS:
540 return Mms.MESSAGE_BOX_DRAFTS;
541 case MMS_OUTBOX_ID:
542 case MMS_OUTBOX:
543 return Mms.MESSAGE_BOX_OUTBOX;
544 default:
545 throw new IllegalArgumentException("bad Arg: " + match);
546 }
547 }
548
549 @Override
550 public int delete(Uri uri, String selection,
551 String[] selectionArgs) {
552 int match = sURLMatcher.match(uri);
553 if (LOCAL_LOGV) {
554 Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
555 }
556
557 String table, extraSelection = null;
558 boolean notify = false;
559
560 switch (match) {
561 case MMS_ALL_ID:
562 case MMS_INBOX_ID:
563 case MMS_SENT_ID:
564 case MMS_DRAFTS_ID:
565 case MMS_OUTBOX_ID:
566 notify = true;
567 table = TABLE_PDU;
568 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
569 break;
570 case MMS_ALL:
571 case MMS_INBOX:
572 case MMS_SENT:
573 case MMS_DRAFTS:
574 case MMS_OUTBOX:
575 notify = true;
576 table = TABLE_PDU;
577 if (match != MMS_ALL) {
578 int msgBox = getMessageBoxByMatch(match);
579 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
580 }
581 break;
582 case MMS_ALL_PART:
583 table = TABLE_PART;
584 break;
585 case MMS_MSG_PART:
586 table = TABLE_PART;
587 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
588 break;
589 case MMS_PART_ID:
590 table = TABLE_PART;
591 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
592 break;
593 case MMS_MSG_ADDR:
594 table = TABLE_ADDR;
595 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
596 break;
597 case MMS_DRM_STORAGE:
598 table = TABLE_DRM;
599 break;
600 default:
601 Log.w(TAG, "No match for URI '" + uri + "'");
602 return 0;
603 }
604
605 String finalSelection = concatSelections(selection, extraSelection);
606 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
607 int deletedRows = 0;
608
609 if (TABLE_PDU.equals(table)) {
610 deletedRows = deleteMessages(getContext(), db, finalSelection,
611 selectionArgs, uri);
612 } else if (TABLE_PART.equals(table)) {
613 deletedRows = deleteParts(db, finalSelection, selectionArgs);
614 } else if (TABLE_DRM.equals(table)) {
615 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
616 } else {
617 deletedRows = db.delete(table, finalSelection, selectionArgs);
618 }
619
620 if ((deletedRows > 0) && notify) {
621 notifyChange();
622 }
623 return deletedRows;
624 }
625
626 static int deleteMessages(Context context, SQLiteDatabase db,
627 String selection, String[] selectionArgs, Uri uri) {
628 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
629 selection, selectionArgs, null, null, null);
630 if (cursor == null) {
631 return 0;
632 }
633
634 try {
635 if (cursor.getCount() == 0) {
636 return 0;
637 }
638
639 while (cursor.moveToNext()) {
640 deleteParts(db, Part.MSG_ID + " = ?",
641 new String[] { String.valueOf(cursor.getLong(0)) });
642 }
643 } finally {
644 cursor.close();
645 }
646
647 int count = db.delete(TABLE_PDU, selection, selectionArgs);
648 if (count > 0) {
649 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
650 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
651 if (LOCAL_LOGV) {
652 Log.v(TAG, "Broadcasting intent: " + intent);
653 }
654 context.sendBroadcast(intent);
655 }
656 return count;
657 }
658
659 private static int deleteParts(SQLiteDatabase db, String selection,
660 String[] selectionArgs) {
661 return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
662 }
663
664 private static int deleteTempDrmData(SQLiteDatabase db, String selection,
665 String[] selectionArgs) {
666 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
667 }
668
669 private static int deleteDataRows(SQLiteDatabase db, String table,
670 String selection, String[] selectionArgs) {
671 Cursor cursor = db.query(table, new String[] { "_data" },
672 selection, selectionArgs, null, null, null);
673 if (cursor == null) {
674 // FIXME: This might be an error, ignore it may cause
675 // unpredictable result.
676 return 0;
677 }
678
679 try {
680 if (cursor.getCount() == 0) {
681 return 0;
682 }
683
684 while (cursor.moveToNext()) {
685 try {
686 // Delete the associated files saved on file-system.
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700687 String path = cursor.getString(0);
688 if (path != null) {
689 new File(path).delete();
690 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800691 } catch (Throwable ex) {
692 Log.e(TAG, ex.getMessage(), ex);
693 }
694 }
695 } finally {
696 cursor.close();
697 }
698
699 return db.delete(table, selection, selectionArgs);
700 }
701
702 @Override
703 public int update(Uri uri, ContentValues values,
704 String selection, String[] selectionArgs) {
Tom Taylor438403e2013-02-21 16:56:01 -0800705 // Don't let anyone update the _data column
706 if (values != null && values.containsKey(Part._DATA)) {
707 return 0;
708 }
Ye Wen496e4b62014-11-19 12:06:05 -0800709 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800710 int match = sURLMatcher.match(uri);
711 if (LOCAL_LOGV) {
712 Log.v(TAG, "Update uri=" + uri + ", match=" + match);
713 }
714
715 boolean notify = false;
716 String msgId = null;
717 String table;
718
719 switch (match) {
720 case MMS_ALL_ID:
721 case MMS_INBOX_ID:
722 case MMS_SENT_ID:
723 case MMS_DRAFTS_ID:
724 case MMS_OUTBOX_ID:
725 msgId = uri.getLastPathSegment();
726 // fall-through
727 case MMS_ALL:
728 case MMS_INBOX:
729 case MMS_SENT:
730 case MMS_DRAFTS:
731 case MMS_OUTBOX:
732 notify = true;
733 table = TABLE_PDU;
734 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700735
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800736 case MMS_MSG_PART:
737 case MMS_PART_ID:
738 table = TABLE_PART;
739 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700740
741 case MMS_PART_RESET_FILE_PERMISSION:
742 String path = getContext().getDir("parts", 0).getPath() + '/' +
743 uri.getPathSegments().get(1);
744 // Reset the file permission back to read for everyone but me.
745 int result = FileUtils.setPermissions(path, 0644, -1, -1);
746 if (LOCAL_LOGV) {
747 Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
748 " for path: " + path);
749 }
750 return 0;
751
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800752 default:
753 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
754 return 0;
755 }
756
757 String extraSelection = null;
758 ContentValues finalValues;
759 if (table.equals(TABLE_PDU)) {
760 // Filter keys that we don't support yet.
761 filterUnsupportedKeys(values);
Ye Wen496e4b62014-11-19 12:06:05 -0800762 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
763 // CREATOR should not be changed by non-SYSTEM/PHONE apps
764 Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
765 " tries to update CREATOR");
766 values.remove(Mms.CREATOR);
767 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800768 finalValues = new ContentValues(values);
769
770 if (msgId != null) {
771 extraSelection = Mms._ID + "=" + msgId;
772 }
773 } else if (table.equals(TABLE_PART)) {
774 finalValues = new ContentValues(values);
775
776 switch (match) {
777 case MMS_MSG_PART:
778 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
779 break;
780 case MMS_PART_ID:
781 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
782 break;
783 default:
784 break;
785 }
786 } else {
787 return 0;
788 }
789
790 String finalSelection = concatSelections(selection, extraSelection);
791 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
792 int count = db.update(table, finalValues, finalSelection, selectionArgs);
793 if (notify && (count > 0)) {
794 notifyChange();
795 }
796 return count;
797 }
798
799 @Override
Wei Huang2914a7a2009-09-23 00:45:36 -0700800 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Wei Huang2914a7a2009-09-23 00:45:36 -0700801 int match = sURLMatcher.match(uri);
802
803 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Tom Taylor8168ff82013-02-19 14:30:23 -0800804 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
Wei Huang2914a7a2009-09-23 00:45:36 -0700805 }
806
Tom Taylor8168ff82013-02-19 14:30:23 -0800807 if (match != MMS_PART_ID) {
808 return null;
Wei Huang2914a7a2009-09-23 00:45:36 -0700809 }
810
Tom Taylor8168ff82013-02-19 14:30:23 -0800811 // Verify that the _data path points to mms data
812 Cursor c = query(uri, new String[]{"_data"}, null, null, null);
813 int count = (c != null) ? c.getCount() : 0;
814 if (count != 1) {
815 // If there is not exactly one result, throw an appropriate
816 // exception.
817 if (c != null) {
818 c.close();
819 }
820 if (count == 0) {
821 throw new FileNotFoundException("No entry for " + uri);
822 }
823 throw new FileNotFoundException("Multiple items at " + uri);
824 }
825
826 c.moveToFirst();
827 int i = c.getColumnIndex("_data");
828 String path = (i >= 0 ? c.getString(i) : null);
829 c.close();
830
831 if (path == null) {
832 return null;
833 }
834 try {
835 File filePath = new File(path);
836 if (!filePath.getCanonicalPath()
837 .startsWith(getContext().getApplicationInfo().dataDir + "/app_parts/")) {
838 return null;
839 }
840 } catch (IOException e) {
841 return null;
842 }
843
844 return openFileHelper(uri, mode);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800845 }
846
847 private void filterUnsupportedKeys(ContentValues values) {
848 // Some columns are unsupported. They should therefore
849 // neither be inserted nor updated. Filter them out.
850 values.remove(Mms.DELIVERY_TIME_TOKEN);
851 values.remove(Mms.SENDER_VISIBILITY);
852 values.remove(Mms.REPLY_CHARGING);
853 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
854 values.remove(Mms.REPLY_CHARGING_DEADLINE);
855 values.remove(Mms.REPLY_CHARGING_ID);
856 values.remove(Mms.REPLY_CHARGING_SIZE);
857 values.remove(Mms.PREVIOUSLY_SENT_BY);
858 values.remove(Mms.PREVIOUSLY_SENT_DATE);
859 values.remove(Mms.STORE);
860 values.remove(Mms.MM_STATE);
861 values.remove(Mms.MM_FLAGS_TOKEN);
862 values.remove(Mms.MM_FLAGS);
863 values.remove(Mms.STORE_STATUS);
864 values.remove(Mms.STORE_STATUS_TEXT);
865 values.remove(Mms.STORED);
866 values.remove(Mms.TOTALS);
867 values.remove(Mms.MBOX_TOTALS);
868 values.remove(Mms.MBOX_TOTALS_TOKEN);
869 values.remove(Mms.QUOTAS);
870 values.remove(Mms.MBOX_QUOTAS);
871 values.remove(Mms.MBOX_QUOTAS_TOKEN);
872 values.remove(Mms.MESSAGE_COUNT);
873 values.remove(Mms.START);
874 values.remove(Mms.DISTRIBUTION_INDICATOR);
875 values.remove(Mms.ELEMENT_DESCRIPTOR);
876 values.remove(Mms.LIMIT);
877 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
878 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
879 values.remove(Mms.STATUS_TEXT);
880 values.remove(Mms.APPLIC_ID);
881 values.remove(Mms.REPLY_APPLIC_ID);
882 values.remove(Mms.AUX_APPLIC_ID);
883 values.remove(Mms.DRM_CONTENT);
884 values.remove(Mms.ADAPTATION_ALLOWED);
885 values.remove(Mms.REPLACE_ID);
886 values.remove(Mms.CANCEL_ID);
887 values.remove(Mms.CANCEL_STATUS);
888
889 // Keys shouldn't be inserted or updated.
890 values.remove(Mms._ID);
891 }
892
893 private void notifyChange() {
894 getContext().getContentResolver().notifyChange(
Amith Yamasani43f9fb22014-09-10 15:56:47 -0700895 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800896 }
897
898 private final static String TAG = "MmsProvider";
899 private final static String VND_ANDROID_MMS = "vnd.android/mms";
900 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
901 private final static boolean DEBUG = false;
Joe Onoratodc946792011-04-07 18:41:15 -0700902 private final static boolean LOCAL_LOGV = false;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800903
904 private static final int MMS_ALL = 0;
905 private static final int MMS_ALL_ID = 1;
906 private static final int MMS_INBOX = 2;
907 private static final int MMS_INBOX_ID = 3;
908 private static final int MMS_SENT = 4;
909 private static final int MMS_SENT_ID = 5;
910 private static final int MMS_DRAFTS = 6;
911 private static final int MMS_DRAFTS_ID = 7;
912 private static final int MMS_OUTBOX = 8;
913 private static final int MMS_OUTBOX_ID = 9;
914 private static final int MMS_ALL_PART = 10;
915 private static final int MMS_MSG_PART = 11;
916 private static final int MMS_PART_ID = 12;
917 private static final int MMS_MSG_ADDR = 13;
918 private static final int MMS_SENDING_RATE = 14;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700919 private static final int MMS_REPORT_STATUS = 15;
920 private static final int MMS_REPORT_REQUEST = 16;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800921 private static final int MMS_DRM_STORAGE = 17;
922 private static final int MMS_DRM_STORAGE_ID = 18;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700923 private static final int MMS_THREADS = 19;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700924 private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800925
926 private static final UriMatcher
927 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
928
929 static {
930 sURLMatcher.addURI("mms", null, MMS_ALL);
931 sURLMatcher.addURI("mms", "#", MMS_ALL_ID);
932 sURLMatcher.addURI("mms", "inbox", MMS_INBOX);
933 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID);
934 sURLMatcher.addURI("mms", "sent", MMS_SENT);
935 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID);
936 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS);
937 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
938 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX);
939 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
940 sURLMatcher.addURI("mms", "part", MMS_ALL_PART);
941 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART);
942 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
943 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR);
944 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE);
945 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS);
946 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
947 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE);
948 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
Tom Taylor6c0ef242009-06-01 12:05:00 -0700949 sURLMatcher.addURI("mms", "threads", MMS_THREADS);
Tom Taylorc2db47d2012-03-27 15:15:39 -0700950 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800951 }
952
953 private SQLiteOpenHelper mOpenHelper;
954
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800955 private static String concatSelections(String selection1, String selection2) {
956 if (TextUtils.isEmpty(selection1)) {
957 return selection2;
958 } else if (TextUtils.isEmpty(selection2)) {
959 return selection1;
960 } else {
961 return selection1 + " AND " + selection2;
962 }
963 }
964}
965