blob: a311788740edcde50119b7529117cf5eb955dd56 [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
Ye Wenfdf4a822015-01-06 13:08:53 -080066 // The name of parts directory. The full dir is "app_parts".
67 private static final String PARTS_DIR_NAME = "parts";
Tom Taylorc2db47d2012-03-27 15:15:39 -070068
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080069 @Override
70 public boolean onCreate() {
Kun Liange3e884d2013-10-02 10:26:25 +080071 setAppOps(AppOpsManager.OP_READ_MMS, AppOpsManager.OP_WRITE_MMS);
Ye Wenb2ce2d32014-07-28 14:49:30 -070072 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080073 return true;
74 }
75
76 @Override
77 public Cursor query(Uri uri, String[] projection,
78 String selection, String[] selectionArgs, String sortOrder) {
79 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
80
81 // Generate the body of the query.
82 int match = sURLMatcher.match(uri);
83 if (LOCAL_LOGV) {
84 Log.v(TAG, "Query uri=" + uri + ", match=" + match);
85 }
86
87 switch (match) {
88 case MMS_ALL:
89 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL);
90 break;
91 case MMS_INBOX:
92 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX);
93 break;
94 case MMS_SENT:
95 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT);
96 break;
97 case MMS_DRAFTS:
98 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS);
99 break;
100 case MMS_OUTBOX:
101 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX);
102 break;
103 case MMS_ALL_ID:
104 qb.setTables(TABLE_PDU);
105 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
106 break;
107 case MMS_INBOX_ID:
108 case MMS_SENT_ID:
109 case MMS_DRAFTS_ID:
110 case MMS_OUTBOX_ID:
111 qb.setTables(TABLE_PDU);
112 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
113 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
114 + getMessageBoxByMatch(match));
115 break;
116 case MMS_ALL_PART:
117 qb.setTables(TABLE_PART);
118 break;
119 case MMS_MSG_PART:
120 qb.setTables(TABLE_PART);
121 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
122 break;
123 case MMS_PART_ID:
124 qb.setTables(TABLE_PART);
125 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
126 break;
127 case MMS_MSG_ADDR:
128 qb.setTables(TABLE_ADDR);
129 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
130 break;
131 case MMS_REPORT_STATUS:
132 /*
133 SELECT DISTINCT address,
134 T.delivery_status AS delivery_status,
135 T.read_status AS read_status
136 FROM addr
137 INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
138 ifnull(P2.st, 0) AS delivery_status,
139 ifnull(P3.read_status, 0) AS read_status
140 FROM pdu P1
141 INNER JOIN pdu P2
142 ON P1.m_id = P2.m_id AND P2.m_type = 134
143 LEFT JOIN pdu P3
144 ON P1.m_id = P3.m_id AND P3.m_type = 136
145 UNION
146 SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
147 ifnull(P2.st, 0) AS delivery_status,
148 ifnull(P3.read_status, 0) AS read_status
149 FROM pdu P1
150 INNER JOIN pdu P3
151 ON P1.m_id = P3.m_id AND P3.m_type = 136
152 LEFT JOIN pdu P2
153 ON P1.m_id = P2.m_id AND P2.m_type = 134) T
154 ON (msg_id = id2 AND type = 151)
155 OR (msg_id = id3 AND type = 137)
156 WHERE T.id1 = ?;
157 */
158 qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" +
159 " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" +
160 " delivery_status, ifnull(P3.read_status, 0) AS" +
161 " read_status FROM pdu P1 INNER JOIN pdu P2 ON" +
162 " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" +
163 " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" +
164 " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" +
165 " AS id3, ifnull(P2.st, 0) AS delivery_status," +
166 " ifnull(P3.read_status, 0) AS read_status FROM" +
167 " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" +
168 " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" +
169 " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" +
170 " OR (msg_id=id3 AND type=137)");
171 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
172 qb.setDistinct(true);
173 break;
174 case MMS_REPORT_REQUEST:
175 /*
176 SELECT address, d_rpt, rr
177 FROM addr join pdu on pdu._id = addr.msg_id
178 WHERE pdu._id = messageId AND addr.type = 151
179 */
180 qb.setTables(TABLE_ADDR + " join " +
181 TABLE_PDU + " on pdu._id = addr.msg_id");
182 qb.appendWhere("pdu._id = " + uri.getLastPathSegment());
183 qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO);
184 break;
185 case MMS_SENDING_RATE:
186 qb.setTables(TABLE_RATE);
187 break;
188 case MMS_DRM_STORAGE_ID:
189 qb.setTables(TABLE_DRM);
190 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
191 break;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700192 case MMS_THREADS:
193 qb.setTables("pdu group by thread_id");
194 break;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800195 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700196 Log.e(TAG, "query: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800197 return null;
198 }
199
200 String finalSortOrder = null;
201 if (TextUtils.isEmpty(sortOrder)) {
202 if (qb.getTables().equals(TABLE_PDU)) {
203 finalSortOrder = Mms.DATE + " DESC";
204 } else if (qb.getTables().equals(TABLE_PART)) {
205 finalSortOrder = Part.SEQ;
206 }
207 } else {
208 finalSortOrder = sortOrder;
209 }
210
Tom Taylor3ad9da42014-01-16 13:57:11 -0800211 Cursor ret;
212 try {
213 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
214 ret = qb.query(db, projection, selection,
215 selectionArgs, null, null, finalSortOrder);
216 } catch (SQLiteException e) {
217 Log.e(TAG, "returning NULL cursor, query: " + uri, e);
218 return null;
219 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800220
221 // TODO: Does this need to be a URI for this provider.
222 ret.setNotificationUri(getContext().getContentResolver(), uri);
223 return ret;
224 }
225
226 private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) {
227 qb.setTables(TABLE_PDU);
228
229 if (msgBox != Mms.MESSAGE_BOX_ALL) {
230 qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
231 }
232 }
233
234 @Override
235 public String getType(Uri uri) {
236 int match = sURLMatcher.match(uri);
237 switch (match) {
238 case MMS_ALL:
239 case MMS_INBOX:
240 case MMS_SENT:
241 case MMS_DRAFTS:
242 case MMS_OUTBOX:
243 return VND_ANDROID_DIR_MMS;
244 case MMS_ALL_ID:
245 case MMS_INBOX_ID:
246 case MMS_SENT_ID:
247 case MMS_DRAFTS_ID:
248 case MMS_OUTBOX_ID:
249 return VND_ANDROID_MMS;
250 case MMS_PART_ID: {
251 Cursor cursor = mOpenHelper.getReadableDatabase().query(
252 TABLE_PART, new String[] { Part.CONTENT_TYPE },
253 Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
254 null, null, null);
255 if (cursor != null) {
256 try {
257 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
258 return cursor.getString(0);
259 } else {
260 Log.e(TAG, "cursor.count() != 1: " + uri);
261 }
262 } finally {
263 cursor.close();
264 }
265 } else {
266 Log.e(TAG, "cursor == null: " + uri);
267 }
268 return "*/*";
269 }
270 case MMS_ALL_PART:
271 case MMS_MSG_PART:
272 case MMS_MSG_ADDR:
273 default:
274 return "*/*";
275 }
276 }
277
278 @Override
279 public Uri insert(Uri uri, ContentValues values) {
Tom Taylor438403e2013-02-21 16:56:01 -0800280 // Don't let anyone insert anything with the _data column
281 if (values != null && values.containsKey(Part._DATA)) {
282 return null;
283 }
Ye Wen496e4b62014-11-19 12:06:05 -0800284 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800285 int msgBox = Mms.MESSAGE_BOX_ALL;
286 boolean notify = true;
287
288 int match = sURLMatcher.match(uri);
289 if (LOCAL_LOGV) {
290 Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
291 }
292
293 String table = TABLE_PDU;
294 switch (match) {
295 case MMS_ALL:
296 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
297 if (msgBoxObj != null) {
298 msgBox = (Integer) msgBoxObj;
299 }
300 else {
301 // default to inbox
302 msgBox = Mms.MESSAGE_BOX_INBOX;
303 }
304 break;
305 case MMS_INBOX:
306 msgBox = Mms.MESSAGE_BOX_INBOX;
307 break;
308 case MMS_SENT:
309 msgBox = Mms.MESSAGE_BOX_SENT;
310 break;
311 case MMS_DRAFTS:
312 msgBox = Mms.MESSAGE_BOX_DRAFTS;
313 break;
314 case MMS_OUTBOX:
315 msgBox = Mms.MESSAGE_BOX_OUTBOX;
316 break;
317 case MMS_MSG_PART:
318 notify = false;
319 table = TABLE_PART;
320 break;
321 case MMS_MSG_ADDR:
322 notify = false;
323 table = TABLE_ADDR;
324 break;
325 case MMS_SENDING_RATE:
326 notify = false;
327 table = TABLE_RATE;
328 break;
329 case MMS_DRM_STORAGE:
330 notify = false;
331 table = TABLE_DRM;
332 break;
333 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700334 Log.e(TAG, "insert: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800335 return null;
336 }
337
338 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
339 ContentValues finalValues;
340 Uri res = Mms.CONTENT_URI;
341 long rowId;
342
343 if (table.equals(TABLE_PDU)) {
344 boolean addDate = !values.containsKey(Mms.DATE);
345 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
346
347 // Filter keys we don't support yet.
348 filterUnsupportedKeys(values);
349
350 // TODO: Should initialValues be validated, e.g. if it
351 // missed some significant keys?
352 finalValues = new ContentValues(values);
353
354 long timeInMillis = System.currentTimeMillis();
355
356 if (addDate) {
357 finalValues.put(Mms.DATE, timeInMillis / 1000L);
358 }
359
360 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
361 finalValues.put(Mms.MESSAGE_BOX, msgBox);
362 }
363
364 if (msgBox != Mms.MESSAGE_BOX_INBOX) {
365 // Mark all non-inbox messages read.
366 finalValues.put(Mms.READ, 1);
367 }
368
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700369 // thread_id
370 Long threadId = values.getAsLong(Mms.THREAD_ID);
371 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
372
Tom Taylor59269962012-05-09 14:42:35 -0700373 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700374 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
375 }
376
Ye Wen496e4b62014-11-19 12:06:05 -0800377 if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
378 // Only SYSTEM or PHONE can set CREATOR
379 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
380 // set CREATOR using the truth on caller.
381 // Note: Inferring package name from UID may include unrelated package names
382 finalValues.put(Telephony.Mms.CREATOR,
383 ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
384 }
385
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800386 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800387 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800388 return null;
389 }
390
391 res = Uri.parse(res + "/" + rowId);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800392 } else if (table.equals(TABLE_ADDR)) {
393 finalValues = new ContentValues(values);
394 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
395
396 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800397 Log.e(TAG, "Failed to insert address");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800398 return null;
399 }
400
401 res = Uri.parse(res + "/addr/" + rowId);
402 } else if (table.equals(TABLE_PART)) {
403 finalValues = new ContentValues(values);
404
405 if (match == MMS_MSG_PART) {
406 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
407 }
408
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700409 String contentType = values.getAsString("ct");
Mark Wagner8e5ee782010-01-04 17:39:06 -0800410
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700411 // text/plain and app application/smil store their "data" inline in the
412 // table so there's no need to create the file
Tom Taylorc2db47d2012-03-27 15:15:39 -0700413 boolean plainText = false;
414 boolean smilText = false;
415 if ("text/plain".equals(contentType)) {
416 plainText = true;
417 } else if ("application/smil".equals(contentType)) {
418 smilText = true;
419 }
Mark Wagner8e5ee782010-01-04 17:39:06 -0800420 if (!plainText && !smilText) {
Tom Taylorc2db47d2012-03-27 15:15:39 -0700421 // Use the filename if possible, otherwise use the current time as the name.
422 String contentLocation = values.getAsString("cl");
423 if (!TextUtils.isEmpty(contentLocation)) {
424 File f = new File(contentLocation);
425 contentLocation = "_" + f.getName();
426 } else {
427 contentLocation = "";
428 }
429
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700430 // Generate the '_data' field of the part with default
431 // permission settings.
Ye Wenfdf4a822015-01-06 13:08:53 -0800432 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
Tom Taylorc2db47d2012-03-27 15:15:39 -0700433 + "/PART_" + System.currentTimeMillis() + contentLocation;
434
435 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
436 // Adds the .fl extension to the filename if contentType is
437 // "application/vnd.oma.drm.message"
438 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
439 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800440
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700441 finalValues.put(Part._DATA, path);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800442
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700443 File partFile = new File(path);
444 if (!partFile.exists()) {
445 try {
446 if (!partFile.createNewFile()) {
447 throw new IllegalStateException(
448 "Unable to create new partFile: " + path);
449 }
Tom Taylorc2db47d2012-03-27 15:15:39 -0700450 // Give everyone rw permission until we encrypt the file
451 // (in PduPersister.persistData). Once the file is encrypted, the
452 // permissions will be set to 0644.
453 int result = FileUtils.setPermissions(path, 0666, -1, -1);
454 if (LOCAL_LOGV) {
455 Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
456 }
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700457 } catch (IOException e) {
458 Log.e(TAG, "createNewFile", e);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800459 throw new IllegalStateException(
460 "Unable to create new partFile: " + path);
461 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800462 }
463 }
464
465 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800466 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800467 return null;
468 }
469
470 res = Uri.parse(res + "/part/" + rowId);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800471
472 // Don't use a trigger for updating the words table because of a bug
473 // in FTS3. The bug is such that the call to get the last inserted
474 // row is incorrect.
475 if (plainText) {
476 // Update the words table with a corresponding row. The words table
477 // allows us to search for words quickly, without scanning the whole
478 // table;
479 ContentValues cv = new ContentValues();
480
481 // we're using the row id of the part table row but we're also using ids
482 // from the sms table so this divides the space into two large chunks.
483 // The row ids from the part table start at 2 << 32.
484 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
485 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
486 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
487 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
488 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
489 }
490
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800491 } else if (table.equals(TABLE_RATE)) {
492 long now = values.getAsLong(Rate.SENT_TIME);
493 long oneHourAgo = now - 1000 * 60 * 60;
494 // Delete all unused rows (time earlier than one hour ago).
495 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
496 db.insert(table, null, values);
497 } else if (table.equals(TABLE_DRM)) {
Ye Wenfdf4a822015-01-06 13:08:53 -0800498 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800499 + "/PART_" + System.currentTimeMillis();
500 finalValues = new ContentValues(1);
501 finalValues.put("_data", path);
502
503 File partFile = new File(path);
504 if (!partFile.exists()) {
505 try {
506 if (!partFile.createNewFile()) {
507 throw new IllegalStateException(
508 "Unable to create new file: " + path);
509 }
510 } catch (IOException e) {
511 Log.e(TAG, "createNewFile", e);
512 throw new IllegalStateException(
513 "Unable to create new file: " + path);
514 }
515 }
516
517 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800518 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800519 return null;
520 }
521 res = Uri.parse(res + "/drm/" + rowId);
522 } else {
523 throw new AssertionError("Unknown table type: " + table);
524 }
525
526 if (notify) {
527 notifyChange();
528 }
529 return res;
530 }
531
532 private int getMessageBoxByMatch(int match) {
533 switch (match) {
534 case MMS_INBOX_ID:
535 case MMS_INBOX:
536 return Mms.MESSAGE_BOX_INBOX;
537 case MMS_SENT_ID:
538 case MMS_SENT:
539 return Mms.MESSAGE_BOX_SENT;
540 case MMS_DRAFTS_ID:
541 case MMS_DRAFTS:
542 return Mms.MESSAGE_BOX_DRAFTS;
543 case MMS_OUTBOX_ID:
544 case MMS_OUTBOX:
545 return Mms.MESSAGE_BOX_OUTBOX;
546 default:
547 throw new IllegalArgumentException("bad Arg: " + match);
548 }
549 }
550
551 @Override
552 public int delete(Uri uri, String selection,
553 String[] selectionArgs) {
554 int match = sURLMatcher.match(uri);
555 if (LOCAL_LOGV) {
556 Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
557 }
558
559 String table, extraSelection = null;
560 boolean notify = false;
561
562 switch (match) {
563 case MMS_ALL_ID:
564 case MMS_INBOX_ID:
565 case MMS_SENT_ID:
566 case MMS_DRAFTS_ID:
567 case MMS_OUTBOX_ID:
568 notify = true;
569 table = TABLE_PDU;
570 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
571 break;
572 case MMS_ALL:
573 case MMS_INBOX:
574 case MMS_SENT:
575 case MMS_DRAFTS:
576 case MMS_OUTBOX:
577 notify = true;
578 table = TABLE_PDU;
579 if (match != MMS_ALL) {
580 int msgBox = getMessageBoxByMatch(match);
581 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
582 }
583 break;
584 case MMS_ALL_PART:
585 table = TABLE_PART;
586 break;
587 case MMS_MSG_PART:
588 table = TABLE_PART;
589 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
590 break;
591 case MMS_PART_ID:
592 table = TABLE_PART;
593 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
594 break;
595 case MMS_MSG_ADDR:
596 table = TABLE_ADDR;
597 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
598 break;
599 case MMS_DRM_STORAGE:
600 table = TABLE_DRM;
601 break;
602 default:
603 Log.w(TAG, "No match for URI '" + uri + "'");
604 return 0;
605 }
606
607 String finalSelection = concatSelections(selection, extraSelection);
608 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
609 int deletedRows = 0;
610
611 if (TABLE_PDU.equals(table)) {
612 deletedRows = deleteMessages(getContext(), db, finalSelection,
613 selectionArgs, uri);
614 } else if (TABLE_PART.equals(table)) {
615 deletedRows = deleteParts(db, finalSelection, selectionArgs);
616 } else if (TABLE_DRM.equals(table)) {
617 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
618 } else {
619 deletedRows = db.delete(table, finalSelection, selectionArgs);
620 }
621
622 if ((deletedRows > 0) && notify) {
623 notifyChange();
624 }
625 return deletedRows;
626 }
627
628 static int deleteMessages(Context context, SQLiteDatabase db,
629 String selection, String[] selectionArgs, Uri uri) {
630 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
631 selection, selectionArgs, null, null, null);
632 if (cursor == null) {
633 return 0;
634 }
635
636 try {
637 if (cursor.getCount() == 0) {
638 return 0;
639 }
640
641 while (cursor.moveToNext()) {
642 deleteParts(db, Part.MSG_ID + " = ?",
643 new String[] { String.valueOf(cursor.getLong(0)) });
644 }
645 } finally {
646 cursor.close();
647 }
648
649 int count = db.delete(TABLE_PDU, selection, selectionArgs);
650 if (count > 0) {
651 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
652 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
653 if (LOCAL_LOGV) {
654 Log.v(TAG, "Broadcasting intent: " + intent);
655 }
656 context.sendBroadcast(intent);
657 }
658 return count;
659 }
660
661 private static int deleteParts(SQLiteDatabase db, String selection,
662 String[] selectionArgs) {
663 return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
664 }
665
666 private static int deleteTempDrmData(SQLiteDatabase db, String selection,
667 String[] selectionArgs) {
668 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
669 }
670
671 private static int deleteDataRows(SQLiteDatabase db, String table,
672 String selection, String[] selectionArgs) {
673 Cursor cursor = db.query(table, new String[] { "_data" },
674 selection, selectionArgs, null, null, null);
675 if (cursor == null) {
676 // FIXME: This might be an error, ignore it may cause
677 // unpredictable result.
678 return 0;
679 }
680
681 try {
682 if (cursor.getCount() == 0) {
683 return 0;
684 }
685
686 while (cursor.moveToNext()) {
687 try {
688 // Delete the associated files saved on file-system.
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700689 String path = cursor.getString(0);
690 if (path != null) {
691 new File(path).delete();
692 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800693 } catch (Throwable ex) {
694 Log.e(TAG, ex.getMessage(), ex);
695 }
696 }
697 } finally {
698 cursor.close();
699 }
700
701 return db.delete(table, selection, selectionArgs);
702 }
703
704 @Override
705 public int update(Uri uri, ContentValues values,
706 String selection, String[] selectionArgs) {
Tom Taylor438403e2013-02-21 16:56:01 -0800707 // Don't let anyone update the _data column
708 if (values != null && values.containsKey(Part._DATA)) {
709 return 0;
710 }
Ye Wen496e4b62014-11-19 12:06:05 -0800711 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800712 int match = sURLMatcher.match(uri);
713 if (LOCAL_LOGV) {
714 Log.v(TAG, "Update uri=" + uri + ", match=" + match);
715 }
716
717 boolean notify = false;
718 String msgId = null;
719 String table;
720
721 switch (match) {
722 case MMS_ALL_ID:
723 case MMS_INBOX_ID:
724 case MMS_SENT_ID:
725 case MMS_DRAFTS_ID:
726 case MMS_OUTBOX_ID:
727 msgId = uri.getLastPathSegment();
728 // fall-through
729 case MMS_ALL:
730 case MMS_INBOX:
731 case MMS_SENT:
732 case MMS_DRAFTS:
733 case MMS_OUTBOX:
734 notify = true;
735 table = TABLE_PDU;
736 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700737
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800738 case MMS_MSG_PART:
739 case MMS_PART_ID:
740 table = TABLE_PART;
741 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700742
743 case MMS_PART_RESET_FILE_PERMISSION:
Ye Wenfdf4a822015-01-06 13:08:53 -0800744 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
Tom Taylorc2db47d2012-03-27 15:15:39 -0700745 uri.getPathSegments().get(1);
746 // Reset the file permission back to read for everyone but me.
747 int result = FileUtils.setPermissions(path, 0644, -1, -1);
748 if (LOCAL_LOGV) {
749 Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
750 " for path: " + path);
751 }
752 return 0;
753
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800754 default:
755 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
756 return 0;
757 }
758
759 String extraSelection = null;
760 ContentValues finalValues;
761 if (table.equals(TABLE_PDU)) {
762 // Filter keys that we don't support yet.
763 filterUnsupportedKeys(values);
Ye Wen496e4b62014-11-19 12:06:05 -0800764 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
765 // CREATOR should not be changed by non-SYSTEM/PHONE apps
766 Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
767 " tries to update CREATOR");
768 values.remove(Mms.CREATOR);
769 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800770 finalValues = new ContentValues(values);
771
772 if (msgId != null) {
773 extraSelection = Mms._ID + "=" + msgId;
774 }
775 } else if (table.equals(TABLE_PART)) {
776 finalValues = new ContentValues(values);
777
778 switch (match) {
779 case MMS_MSG_PART:
780 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
781 break;
782 case MMS_PART_ID:
783 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
784 break;
785 default:
786 break;
787 }
788 } else {
789 return 0;
790 }
791
792 String finalSelection = concatSelections(selection, extraSelection);
793 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
794 int count = db.update(table, finalValues, finalSelection, selectionArgs);
795 if (notify && (count > 0)) {
796 notifyChange();
797 }
798 return count;
799 }
800
801 @Override
Wei Huang2914a7a2009-09-23 00:45:36 -0700802 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Wei Huang2914a7a2009-09-23 00:45:36 -0700803 int match = sURLMatcher.match(uri);
804
805 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Tom Taylor8168ff82013-02-19 14:30:23 -0800806 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
Wei Huang2914a7a2009-09-23 00:45:36 -0700807 }
808
Tom Taylor8168ff82013-02-19 14:30:23 -0800809 if (match != MMS_PART_ID) {
810 return null;
Wei Huang2914a7a2009-09-23 00:45:36 -0700811 }
812
Tom Taylor8168ff82013-02-19 14:30:23 -0800813 // Verify that the _data path points to mms data
814 Cursor c = query(uri, new String[]{"_data"}, null, null, null);
815 int count = (c != null) ? c.getCount() : 0;
816 if (count != 1) {
817 // If there is not exactly one result, throw an appropriate
818 // exception.
819 if (c != null) {
820 c.close();
821 }
822 if (count == 0) {
823 throw new FileNotFoundException("No entry for " + uri);
824 }
825 throw new FileNotFoundException("Multiple items at " + uri);
826 }
827
828 c.moveToFirst();
829 int i = c.getColumnIndex("_data");
830 String path = (i >= 0 ? c.getString(i) : null);
831 c.close();
832
833 if (path == null) {
834 return null;
835 }
836 try {
837 File filePath = new File(path);
838 if (!filePath.getCanonicalPath()
Ye Wenfdf4a822015-01-06 13:08:53 -0800839 .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getPath())) {
840 Log.e(TAG, "openFile: path "
841 + filePath.getCanonicalPath()
842 + " does not start with "
843 + getContext().getDir(PARTS_DIR_NAME, 0).getPath());
844 // Don't care return value
845 filePath.delete();
Tom Taylor8168ff82013-02-19 14:30:23 -0800846 return null;
847 }
848 } catch (IOException e) {
Ye Wenfdf4a822015-01-06 13:08:53 -0800849 Log.e(TAG, "openFile: create path failed " + e, e);
Tom Taylor8168ff82013-02-19 14:30:23 -0800850 return null;
851 }
852
853 return openFileHelper(uri, mode);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800854 }
855
856 private void filterUnsupportedKeys(ContentValues values) {
857 // Some columns are unsupported. They should therefore
858 // neither be inserted nor updated. Filter them out.
859 values.remove(Mms.DELIVERY_TIME_TOKEN);
860 values.remove(Mms.SENDER_VISIBILITY);
861 values.remove(Mms.REPLY_CHARGING);
862 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
863 values.remove(Mms.REPLY_CHARGING_DEADLINE);
864 values.remove(Mms.REPLY_CHARGING_ID);
865 values.remove(Mms.REPLY_CHARGING_SIZE);
866 values.remove(Mms.PREVIOUSLY_SENT_BY);
867 values.remove(Mms.PREVIOUSLY_SENT_DATE);
868 values.remove(Mms.STORE);
869 values.remove(Mms.MM_STATE);
870 values.remove(Mms.MM_FLAGS_TOKEN);
871 values.remove(Mms.MM_FLAGS);
872 values.remove(Mms.STORE_STATUS);
873 values.remove(Mms.STORE_STATUS_TEXT);
874 values.remove(Mms.STORED);
875 values.remove(Mms.TOTALS);
876 values.remove(Mms.MBOX_TOTALS);
877 values.remove(Mms.MBOX_TOTALS_TOKEN);
878 values.remove(Mms.QUOTAS);
879 values.remove(Mms.MBOX_QUOTAS);
880 values.remove(Mms.MBOX_QUOTAS_TOKEN);
881 values.remove(Mms.MESSAGE_COUNT);
882 values.remove(Mms.START);
883 values.remove(Mms.DISTRIBUTION_INDICATOR);
884 values.remove(Mms.ELEMENT_DESCRIPTOR);
885 values.remove(Mms.LIMIT);
886 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
887 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
888 values.remove(Mms.STATUS_TEXT);
889 values.remove(Mms.APPLIC_ID);
890 values.remove(Mms.REPLY_APPLIC_ID);
891 values.remove(Mms.AUX_APPLIC_ID);
892 values.remove(Mms.DRM_CONTENT);
893 values.remove(Mms.ADAPTATION_ALLOWED);
894 values.remove(Mms.REPLACE_ID);
895 values.remove(Mms.CANCEL_ID);
896 values.remove(Mms.CANCEL_STATUS);
897
898 // Keys shouldn't be inserted or updated.
899 values.remove(Mms._ID);
900 }
901
902 private void notifyChange() {
903 getContext().getContentResolver().notifyChange(
Amith Yamasani43f9fb22014-09-10 15:56:47 -0700904 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800905 }
906
907 private final static String TAG = "MmsProvider";
908 private final static String VND_ANDROID_MMS = "vnd.android/mms";
909 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
910 private final static boolean DEBUG = false;
Joe Onoratodc946792011-04-07 18:41:15 -0700911 private final static boolean LOCAL_LOGV = false;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800912
913 private static final int MMS_ALL = 0;
914 private static final int MMS_ALL_ID = 1;
915 private static final int MMS_INBOX = 2;
916 private static final int MMS_INBOX_ID = 3;
917 private static final int MMS_SENT = 4;
918 private static final int MMS_SENT_ID = 5;
919 private static final int MMS_DRAFTS = 6;
920 private static final int MMS_DRAFTS_ID = 7;
921 private static final int MMS_OUTBOX = 8;
922 private static final int MMS_OUTBOX_ID = 9;
923 private static final int MMS_ALL_PART = 10;
924 private static final int MMS_MSG_PART = 11;
925 private static final int MMS_PART_ID = 12;
926 private static final int MMS_MSG_ADDR = 13;
927 private static final int MMS_SENDING_RATE = 14;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700928 private static final int MMS_REPORT_STATUS = 15;
929 private static final int MMS_REPORT_REQUEST = 16;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800930 private static final int MMS_DRM_STORAGE = 17;
931 private static final int MMS_DRM_STORAGE_ID = 18;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700932 private static final int MMS_THREADS = 19;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700933 private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800934
935 private static final UriMatcher
936 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
937
938 static {
939 sURLMatcher.addURI("mms", null, MMS_ALL);
940 sURLMatcher.addURI("mms", "#", MMS_ALL_ID);
941 sURLMatcher.addURI("mms", "inbox", MMS_INBOX);
942 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID);
943 sURLMatcher.addURI("mms", "sent", MMS_SENT);
944 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID);
945 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS);
946 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
947 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX);
948 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
949 sURLMatcher.addURI("mms", "part", MMS_ALL_PART);
950 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART);
951 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
952 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR);
953 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE);
954 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS);
955 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
956 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE);
957 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
Tom Taylor6c0ef242009-06-01 12:05:00 -0700958 sURLMatcher.addURI("mms", "threads", MMS_THREADS);
Tom Taylorc2db47d2012-03-27 15:15:39 -0700959 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800960 }
961
962 private SQLiteOpenHelper mOpenHelper;
963
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800964 private static String concatSelections(String selection1, String selection2) {
965 if (TextUtils.isEmpty(selection1)) {
966 return selection2;
967 } else if (TextUtils.isEmpty(selection2)) {
968 return selection1;
969 } else {
970 return selection1 + " AND " + selection2;
971 }
972 }
973}
974