blob: da8f1933744718aa2b7c96930711e9de0d833a00 [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
Teow Wan Yee592ad6d2016-09-20 16:05:14 +080019import android.annotation.NonNull;
Dianne Hackbornf27792f2013-02-04 18:26:53 -080020import android.app.AppOpsManager;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080021import android.content.ContentProvider;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.UriMatcher;
26import android.database.Cursor;
27import android.database.sqlite.SQLiteDatabase;
Tom Taylor3ad9da42014-01-16 13:57:11 -080028import android.database.sqlite.SQLiteException;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080029import android.database.sqlite.SQLiteOpenHelper;
30import android.database.sqlite.SQLiteQueryBuilder;
31import android.net.Uri;
Ye Wen496e4b62014-11-19 12:06:05 -080032import android.os.Binder;
Tom Taylorc2db47d2012-03-27 15:15:39 -070033import android.os.FileUtils;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080034import android.os.ParcelFileDescriptor;
Amith Yamasani43f9fb22014-09-10 15:56:47 -070035import android.os.UserHandle;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080036import android.provider.BaseColumns;
Mark Wagner8e5ee782010-01-04 17:39:06 -080037import android.provider.Telephony;
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -070038import android.provider.Telephony.CanonicalAddressesColumns;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080039import android.provider.Telephony.Mms;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080040import android.provider.Telephony.Mms.Addr;
41import android.provider.Telephony.Mms.Part;
42import android.provider.Telephony.Mms.Rate;
Ye Wen496e4b62014-11-19 12:06:05 -080043import android.provider.Telephony.MmsSms;
44import android.provider.Telephony.Threads;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080045import android.text.TextUtils;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080046import android.util.Log;
47
Tom Taylorb1bae652010-03-08 16:33:42 -080048import com.google.android.mms.pdu.PduHeaders;
Tom Taylorc2db47d2012-03-27 15:15:39 -070049import com.google.android.mms.util.DownloadDrmHelper;
Tom Taylorc71e7702010-01-28 09:23:12 -080050
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080051import java.io.File;
52import java.io.FileNotFoundException;
53import java.io.IOException;
Amith Yamasani43f9fb22014-09-10 15:56:47 -070054
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080055/**
56 * The class to provide base facility to access MMS related content,
57 * which is stored in a SQLite database and in the file system.
58 */
59public class MmsProvider extends ContentProvider {
60 static final String TABLE_PDU = "pdu";
61 static final String TABLE_ADDR = "addr";
62 static final String TABLE_PART = "part";
63 static final String TABLE_RATE = "rate";
64 static final String TABLE_DRM = "drm";
Mark Wagner8e5ee782010-01-04 17:39:06 -080065 static final String TABLE_WORDS = "words";
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080066
Ye Wenfdf4a822015-01-06 13:08:53 -080067 // The name of parts directory. The full dir is "app_parts".
68 private static final String PARTS_DIR_NAME = "parts";
Tom Taylorc2db47d2012-03-27 15:15:39 -070069
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080070 @Override
71 public boolean onCreate() {
Kun Liange3e884d2013-10-02 10:26:25 +080072 setAppOps(AppOpsManager.OP_READ_MMS, AppOpsManager.OP_WRITE_MMS);
Ye Wenb2ce2d32014-07-28 14:49:30 -070073 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080074 return true;
75 }
76
77 @Override
78 public Cursor query(Uri uri, String[] projection,
79 String selection, String[] selectionArgs, String sortOrder) {
80 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
81
82 // Generate the body of the query.
83 int match = sURLMatcher.match(uri);
84 if (LOCAL_LOGV) {
85 Log.v(TAG, "Query uri=" + uri + ", match=" + match);
86 }
87
88 switch (match) {
89 case MMS_ALL:
90 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL);
91 break;
92 case MMS_INBOX:
93 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX);
94 break;
95 case MMS_SENT:
96 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT);
97 break;
98 case MMS_DRAFTS:
99 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS);
100 break;
101 case MMS_OUTBOX:
102 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX);
103 break;
104 case MMS_ALL_ID:
105 qb.setTables(TABLE_PDU);
106 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
107 break;
108 case MMS_INBOX_ID:
109 case MMS_SENT_ID:
110 case MMS_DRAFTS_ID:
111 case MMS_OUTBOX_ID:
112 qb.setTables(TABLE_PDU);
113 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
114 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
115 + getMessageBoxByMatch(match));
116 break;
117 case MMS_ALL_PART:
118 qb.setTables(TABLE_PART);
119 break;
120 case MMS_MSG_PART:
121 qb.setTables(TABLE_PART);
122 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
123 break;
124 case MMS_PART_ID:
125 qb.setTables(TABLE_PART);
126 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
127 break;
128 case MMS_MSG_ADDR:
129 qb.setTables(TABLE_ADDR);
130 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
131 break;
132 case MMS_REPORT_STATUS:
133 /*
134 SELECT DISTINCT address,
135 T.delivery_status AS delivery_status,
136 T.read_status AS read_status
137 FROM addr
138 INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
139 ifnull(P2.st, 0) AS delivery_status,
140 ifnull(P3.read_status, 0) AS read_status
141 FROM pdu P1
142 INNER JOIN pdu P2
143 ON P1.m_id = P2.m_id AND P2.m_type = 134
144 LEFT JOIN pdu P3
145 ON P1.m_id = P3.m_id AND P3.m_type = 136
146 UNION
147 SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
148 ifnull(P2.st, 0) AS delivery_status,
149 ifnull(P3.read_status, 0) AS read_status
150 FROM pdu P1
151 INNER JOIN pdu P3
152 ON P1.m_id = P3.m_id AND P3.m_type = 136
153 LEFT JOIN pdu P2
154 ON P1.m_id = P2.m_id AND P2.m_type = 134) T
155 ON (msg_id = id2 AND type = 151)
156 OR (msg_id = id3 AND type = 137)
157 WHERE T.id1 = ?;
158 */
159 qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" +
160 " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" +
161 " delivery_status, ifnull(P3.read_status, 0) AS" +
162 " read_status FROM pdu P1 INNER JOIN pdu P2 ON" +
163 " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" +
164 " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" +
165 " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" +
166 " AS id3, ifnull(P2.st, 0) AS delivery_status," +
167 " ifnull(P3.read_status, 0) AS read_status FROM" +
168 " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" +
169 " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" +
170 " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" +
171 " OR (msg_id=id3 AND type=137)");
172 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
173 qb.setDistinct(true);
174 break;
175 case MMS_REPORT_REQUEST:
176 /*
177 SELECT address, d_rpt, rr
178 FROM addr join pdu on pdu._id = addr.msg_id
179 WHERE pdu._id = messageId AND addr.type = 151
180 */
181 qb.setTables(TABLE_ADDR + " join " +
182 TABLE_PDU + " on pdu._id = addr.msg_id");
183 qb.appendWhere("pdu._id = " + uri.getLastPathSegment());
184 qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO);
185 break;
186 case MMS_SENDING_RATE:
187 qb.setTables(TABLE_RATE);
188 break;
189 case MMS_DRM_STORAGE_ID:
190 qb.setTables(TABLE_DRM);
191 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
192 break;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700193 case MMS_THREADS:
194 qb.setTables("pdu group by thread_id");
195 break;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800196 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700197 Log.e(TAG, "query: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800198 return null;
199 }
200
201 String finalSortOrder = null;
202 if (TextUtils.isEmpty(sortOrder)) {
203 if (qb.getTables().equals(TABLE_PDU)) {
204 finalSortOrder = Mms.DATE + " DESC";
205 } else if (qb.getTables().equals(TABLE_PART)) {
206 finalSortOrder = Part.SEQ;
207 }
208 } else {
209 finalSortOrder = sortOrder;
210 }
211
Tom Taylor3ad9da42014-01-16 13:57:11 -0800212 Cursor ret;
213 try {
214 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
215 ret = qb.query(db, projection, selection,
216 selectionArgs, null, null, finalSortOrder);
217 } catch (SQLiteException e) {
218 Log.e(TAG, "returning NULL cursor, query: " + uri, e);
219 return null;
220 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800221
222 // TODO: Does this need to be a URI for this provider.
223 ret.setNotificationUri(getContext().getContentResolver(), uri);
224 return ret;
225 }
226
227 private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) {
228 qb.setTables(TABLE_PDU);
229
230 if (msgBox != Mms.MESSAGE_BOX_ALL) {
231 qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
232 }
233 }
234
235 @Override
236 public String getType(Uri uri) {
237 int match = sURLMatcher.match(uri);
238 switch (match) {
239 case MMS_ALL:
240 case MMS_INBOX:
241 case MMS_SENT:
242 case MMS_DRAFTS:
243 case MMS_OUTBOX:
244 return VND_ANDROID_DIR_MMS;
245 case MMS_ALL_ID:
246 case MMS_INBOX_ID:
247 case MMS_SENT_ID:
248 case MMS_DRAFTS_ID:
249 case MMS_OUTBOX_ID:
250 return VND_ANDROID_MMS;
251 case MMS_PART_ID: {
252 Cursor cursor = mOpenHelper.getReadableDatabase().query(
253 TABLE_PART, new String[] { Part.CONTENT_TYPE },
254 Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
255 null, null, null);
256 if (cursor != null) {
257 try {
258 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
259 return cursor.getString(0);
260 } else {
261 Log.e(TAG, "cursor.count() != 1: " + uri);
262 }
263 } finally {
264 cursor.close();
265 }
266 } else {
267 Log.e(TAG, "cursor == null: " + uri);
268 }
269 return "*/*";
270 }
271 case MMS_ALL_PART:
272 case MMS_MSG_PART:
273 case MMS_MSG_ADDR:
274 default:
275 return "*/*";
276 }
277 }
278
279 @Override
280 public Uri insert(Uri uri, ContentValues values) {
Tom Taylor438403e2013-02-21 16:56:01 -0800281 // Don't let anyone insert anything with the _data column
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800282 // The _data column is filled internally in MmsProvider, so this check is just to avoid
283 // it from being inadvertently set. This is not supposed to be a protection against
284 // malicious attack, since sql injection could still be attempted to bypass the check. On
285 // the other hand, the MmsProvider does verify that the _data column has an allowed value
286 // before opening any uri/files.
Tom Taylor438403e2013-02-21 16:56:01 -0800287 if (values != null && values.containsKey(Part._DATA)) {
288 return null;
289 }
Ye Wen496e4b62014-11-19 12:06:05 -0800290 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800291 int msgBox = Mms.MESSAGE_BOX_ALL;
292 boolean notify = true;
293
294 int match = sURLMatcher.match(uri);
295 if (LOCAL_LOGV) {
296 Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
297 }
298
299 String table = TABLE_PDU;
300 switch (match) {
301 case MMS_ALL:
302 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
303 if (msgBoxObj != null) {
304 msgBox = (Integer) msgBoxObj;
305 }
306 else {
307 // default to inbox
308 msgBox = Mms.MESSAGE_BOX_INBOX;
309 }
310 break;
311 case MMS_INBOX:
312 msgBox = Mms.MESSAGE_BOX_INBOX;
313 break;
314 case MMS_SENT:
315 msgBox = Mms.MESSAGE_BOX_SENT;
316 break;
317 case MMS_DRAFTS:
318 msgBox = Mms.MESSAGE_BOX_DRAFTS;
319 break;
320 case MMS_OUTBOX:
321 msgBox = Mms.MESSAGE_BOX_OUTBOX;
322 break;
323 case MMS_MSG_PART:
324 notify = false;
325 table = TABLE_PART;
326 break;
327 case MMS_MSG_ADDR:
328 notify = false;
329 table = TABLE_ADDR;
330 break;
331 case MMS_SENDING_RATE:
332 notify = false;
333 table = TABLE_RATE;
334 break;
335 case MMS_DRM_STORAGE:
336 notify = false;
337 table = TABLE_DRM;
338 break;
339 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700340 Log.e(TAG, "insert: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800341 return null;
342 }
343
344 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
345 ContentValues finalValues;
346 Uri res = Mms.CONTENT_URI;
347 long rowId;
348
349 if (table.equals(TABLE_PDU)) {
350 boolean addDate = !values.containsKey(Mms.DATE);
351 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
352
353 // Filter keys we don't support yet.
354 filterUnsupportedKeys(values);
355
356 // TODO: Should initialValues be validated, e.g. if it
357 // missed some significant keys?
358 finalValues = new ContentValues(values);
359
360 long timeInMillis = System.currentTimeMillis();
361
362 if (addDate) {
363 finalValues.put(Mms.DATE, timeInMillis / 1000L);
364 }
365
366 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
367 finalValues.put(Mms.MESSAGE_BOX, msgBox);
368 }
369
370 if (msgBox != Mms.MESSAGE_BOX_INBOX) {
371 // Mark all non-inbox messages read.
372 finalValues.put(Mms.READ, 1);
373 }
374
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700375 // thread_id
376 Long threadId = values.getAsLong(Mms.THREAD_ID);
377 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
378
Tom Taylor59269962012-05-09 14:42:35 -0700379 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700380 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
381 }
382
Ye Wen496e4b62014-11-19 12:06:05 -0800383 if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
384 // Only SYSTEM or PHONE can set CREATOR
385 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
386 // set CREATOR using the truth on caller.
387 // Note: Inferring package name from UID may include unrelated package names
388 finalValues.put(Telephony.Mms.CREATOR,
389 ProviderUtil.getPackageNamesByUid(getContext(), callerUid));
390 }
391
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800392 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800393 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800394 return null;
395 }
396
397 res = Uri.parse(res + "/" + rowId);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800398 } else if (table.equals(TABLE_ADDR)) {
399 finalValues = new ContentValues(values);
400 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
401
402 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800403 Log.e(TAG, "Failed to insert address");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800404 return null;
405 }
406
407 res = Uri.parse(res + "/addr/" + rowId);
408 } else if (table.equals(TABLE_PART)) {
409 finalValues = new ContentValues(values);
410
411 if (match == MMS_MSG_PART) {
412 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
413 }
414
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700415 String contentType = values.getAsString("ct");
Mark Wagner8e5ee782010-01-04 17:39:06 -0800416
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700417 // text/plain and app application/smil store their "data" inline in the
418 // table so there's no need to create the file
Tom Taylorc2db47d2012-03-27 15:15:39 -0700419 boolean plainText = false;
420 boolean smilText = false;
421 if ("text/plain".equals(contentType)) {
422 plainText = true;
423 } else if ("application/smil".equals(contentType)) {
424 smilText = true;
425 }
Mark Wagner8e5ee782010-01-04 17:39:06 -0800426 if (!plainText && !smilText) {
Tom Taylorc2db47d2012-03-27 15:15:39 -0700427 // Use the filename if possible, otherwise use the current time as the name.
428 String contentLocation = values.getAsString("cl");
429 if (!TextUtils.isEmpty(contentLocation)) {
430 File f = new File(contentLocation);
431 contentLocation = "_" + f.getName();
432 } else {
433 contentLocation = "";
434 }
435
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700436 // Generate the '_data' field of the part with default
437 // permission settings.
Ye Wenfdf4a822015-01-06 13:08:53 -0800438 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
Tom Taylorc2db47d2012-03-27 15:15:39 -0700439 + "/PART_" + System.currentTimeMillis() + contentLocation;
440
441 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
442 // Adds the .fl extension to the filename if contentType is
443 // "application/vnd.oma.drm.message"
444 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
445 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800446
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700447 finalValues.put(Part._DATA, path);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800448
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700449 File partFile = new File(path);
450 if (!partFile.exists()) {
451 try {
452 if (!partFile.createNewFile()) {
453 throw new IllegalStateException(
454 "Unable to create new partFile: " + path);
455 }
Tom Taylorc2db47d2012-03-27 15:15:39 -0700456 // Give everyone rw permission until we encrypt the file
457 // (in PduPersister.persistData). Once the file is encrypted, the
458 // permissions will be set to 0644.
459 int result = FileUtils.setPermissions(path, 0666, -1, -1);
460 if (LOCAL_LOGV) {
461 Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
462 }
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700463 } catch (IOException e) {
464 Log.e(TAG, "createNewFile", e);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800465 throw new IllegalStateException(
466 "Unable to create new partFile: " + path);
467 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800468 }
469 }
470
471 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800472 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800473 return null;
474 }
475
476 res = Uri.parse(res + "/part/" + rowId);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800477
478 // Don't use a trigger for updating the words table because of a bug
479 // in FTS3. The bug is such that the call to get the last inserted
480 // row is incorrect.
481 if (plainText) {
482 // Update the words table with a corresponding row. The words table
483 // allows us to search for words quickly, without scanning the whole
484 // table;
485 ContentValues cv = new ContentValues();
486
487 // we're using the row id of the part table row but we're also using ids
488 // from the sms table so this divides the space into two large chunks.
489 // The row ids from the part table start at 2 << 32.
490 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
491 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
492 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
493 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
494 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
495 }
496
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800497 } else if (table.equals(TABLE_RATE)) {
498 long now = values.getAsLong(Rate.SENT_TIME);
499 long oneHourAgo = now - 1000 * 60 * 60;
500 // Delete all unused rows (time earlier than one hour ago).
501 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
502 db.insert(table, null, values);
503 } else if (table.equals(TABLE_DRM)) {
Ye Wenfdf4a822015-01-06 13:08:53 -0800504 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800505 + "/PART_" + System.currentTimeMillis();
506 finalValues = new ContentValues(1);
507 finalValues.put("_data", path);
508
509 File partFile = new File(path);
510 if (!partFile.exists()) {
511 try {
512 if (!partFile.createNewFile()) {
513 throw new IllegalStateException(
514 "Unable to create new file: " + path);
515 }
516 } catch (IOException e) {
517 Log.e(TAG, "createNewFile", e);
518 throw new IllegalStateException(
519 "Unable to create new file: " + path);
520 }
521 }
522
523 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wen496e4b62014-11-19 12:06:05 -0800524 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800525 return null;
526 }
527 res = Uri.parse(res + "/drm/" + rowId);
528 } else {
529 throw new AssertionError("Unknown table type: " + table);
530 }
531
532 if (notify) {
533 notifyChange();
534 }
535 return res;
536 }
537
538 private int getMessageBoxByMatch(int match) {
539 switch (match) {
540 case MMS_INBOX_ID:
541 case MMS_INBOX:
542 return Mms.MESSAGE_BOX_INBOX;
543 case MMS_SENT_ID:
544 case MMS_SENT:
545 return Mms.MESSAGE_BOX_SENT;
546 case MMS_DRAFTS_ID:
547 case MMS_DRAFTS:
548 return Mms.MESSAGE_BOX_DRAFTS;
549 case MMS_OUTBOX_ID:
550 case MMS_OUTBOX:
551 return Mms.MESSAGE_BOX_OUTBOX;
552 default:
553 throw new IllegalArgumentException("bad Arg: " + match);
554 }
555 }
556
557 @Override
558 public int delete(Uri uri, String selection,
559 String[] selectionArgs) {
560 int match = sURLMatcher.match(uri);
561 if (LOCAL_LOGV) {
562 Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
563 }
564
565 String table, extraSelection = null;
566 boolean notify = false;
567
568 switch (match) {
569 case MMS_ALL_ID:
570 case MMS_INBOX_ID:
571 case MMS_SENT_ID:
572 case MMS_DRAFTS_ID:
573 case MMS_OUTBOX_ID:
574 notify = true;
575 table = TABLE_PDU;
576 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
577 break;
578 case MMS_ALL:
579 case MMS_INBOX:
580 case MMS_SENT:
581 case MMS_DRAFTS:
582 case MMS_OUTBOX:
583 notify = true;
584 table = TABLE_PDU;
585 if (match != MMS_ALL) {
586 int msgBox = getMessageBoxByMatch(match);
587 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
588 }
589 break;
590 case MMS_ALL_PART:
591 table = TABLE_PART;
592 break;
593 case MMS_MSG_PART:
594 table = TABLE_PART;
595 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
596 break;
597 case MMS_PART_ID:
598 table = TABLE_PART;
599 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
600 break;
601 case MMS_MSG_ADDR:
602 table = TABLE_ADDR;
603 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
604 break;
605 case MMS_DRM_STORAGE:
606 table = TABLE_DRM;
607 break;
608 default:
609 Log.w(TAG, "No match for URI '" + uri + "'");
610 return 0;
611 }
612
613 String finalSelection = concatSelections(selection, extraSelection);
614 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
615 int deletedRows = 0;
616
617 if (TABLE_PDU.equals(table)) {
618 deletedRows = deleteMessages(getContext(), db, finalSelection,
619 selectionArgs, uri);
620 } else if (TABLE_PART.equals(table)) {
621 deletedRows = deleteParts(db, finalSelection, selectionArgs);
622 } else if (TABLE_DRM.equals(table)) {
623 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
624 } else {
625 deletedRows = db.delete(table, finalSelection, selectionArgs);
626 }
627
628 if ((deletedRows > 0) && notify) {
629 notifyChange();
630 }
631 return deletedRows;
632 }
633
634 static int deleteMessages(Context context, SQLiteDatabase db,
635 String selection, String[] selectionArgs, Uri uri) {
636 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
637 selection, selectionArgs, null, null, null);
638 if (cursor == null) {
639 return 0;
640 }
641
642 try {
643 if (cursor.getCount() == 0) {
644 return 0;
645 }
646
647 while (cursor.moveToNext()) {
648 deleteParts(db, Part.MSG_ID + " = ?",
649 new String[] { String.valueOf(cursor.getLong(0)) });
650 }
651 } finally {
652 cursor.close();
653 }
654
655 int count = db.delete(TABLE_PDU, selection, selectionArgs);
656 if (count > 0) {
657 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
658 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
659 if (LOCAL_LOGV) {
660 Log.v(TAG, "Broadcasting intent: " + intent);
661 }
662 context.sendBroadcast(intent);
663 }
664 return count;
665 }
666
667 private static int deleteParts(SQLiteDatabase db, String selection,
668 String[] selectionArgs) {
669 return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
670 }
671
672 private static int deleteTempDrmData(SQLiteDatabase db, String selection,
673 String[] selectionArgs) {
674 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
675 }
676
677 private static int deleteDataRows(SQLiteDatabase db, String table,
678 String selection, String[] selectionArgs) {
679 Cursor cursor = db.query(table, new String[] { "_data" },
680 selection, selectionArgs, null, null, null);
681 if (cursor == null) {
682 // FIXME: This might be an error, ignore it may cause
683 // unpredictable result.
684 return 0;
685 }
686
687 try {
688 if (cursor.getCount() == 0) {
689 return 0;
690 }
691
692 while (cursor.moveToNext()) {
693 try {
694 // Delete the associated files saved on file-system.
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700695 String path = cursor.getString(0);
696 if (path != null) {
697 new File(path).delete();
698 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800699 } catch (Throwable ex) {
700 Log.e(TAG, ex.getMessage(), ex);
701 }
702 }
703 } finally {
704 cursor.close();
705 }
706
707 return db.delete(table, selection, selectionArgs);
708 }
709
710 @Override
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800711 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
712 // The _data column is filled internally in MmsProvider, so this check is just to avoid
713 // it from being inadvertently set. This is not supposed to be a protection against
714 // malicious attack, since sql injection could still be attempted to bypass the check. On
715 // the other hand, the MmsProvider does verify that the _data column has an allowed value
716 // before opening any uri/files.
Tom Taylor438403e2013-02-21 16:56:01 -0800717 if (values != null && values.containsKey(Part._DATA)) {
718 return 0;
719 }
Ye Wen496e4b62014-11-19 12:06:05 -0800720 final int callerUid = Binder.getCallingUid();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800721 int match = sURLMatcher.match(uri);
722 if (LOCAL_LOGV) {
723 Log.v(TAG, "Update uri=" + uri + ", match=" + match);
724 }
725
726 boolean notify = false;
727 String msgId = null;
728 String table;
729
730 switch (match) {
731 case MMS_ALL_ID:
732 case MMS_INBOX_ID:
733 case MMS_SENT_ID:
734 case MMS_DRAFTS_ID:
735 case MMS_OUTBOX_ID:
736 msgId = uri.getLastPathSegment();
737 // fall-through
738 case MMS_ALL:
739 case MMS_INBOX:
740 case MMS_SENT:
741 case MMS_DRAFTS:
742 case MMS_OUTBOX:
743 notify = true;
744 table = TABLE_PDU;
745 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700746
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800747 case MMS_MSG_PART:
748 case MMS_PART_ID:
749 table = TABLE_PART;
750 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700751
752 case MMS_PART_RESET_FILE_PERMISSION:
Ye Wenfdf4a822015-01-06 13:08:53 -0800753 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
Tom Taylorc2db47d2012-03-27 15:15:39 -0700754 uri.getPathSegments().get(1);
755 // Reset the file permission back to read for everyone but me.
756 int result = FileUtils.setPermissions(path, 0644, -1, -1);
757 if (LOCAL_LOGV) {
758 Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
759 " for path: " + path);
760 }
761 return 0;
762
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800763 default:
764 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
765 return 0;
766 }
767
768 String extraSelection = null;
769 ContentValues finalValues;
770 if (table.equals(TABLE_PDU)) {
771 // Filter keys that we don't support yet.
772 filterUnsupportedKeys(values);
Ye Wen496e4b62014-11-19 12:06:05 -0800773 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
774 // CREATOR should not be changed by non-SYSTEM/PHONE apps
775 Log.w(TAG, ProviderUtil.getPackageNamesByUid(getContext(), callerUid) +
776 " tries to update CREATOR");
777 values.remove(Mms.CREATOR);
778 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800779 finalValues = new ContentValues(values);
780
781 if (msgId != null) {
782 extraSelection = Mms._ID + "=" + msgId;
783 }
784 } else if (table.equals(TABLE_PART)) {
785 finalValues = new ContentValues(values);
786
787 switch (match) {
788 case MMS_MSG_PART:
789 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
790 break;
791 case MMS_PART_ID:
792 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
793 break;
794 default:
795 break;
796 }
797 } else {
798 return 0;
799 }
800
801 String finalSelection = concatSelections(selection, extraSelection);
802 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
803 int count = db.update(table, finalValues, finalSelection, selectionArgs);
804 if (notify && (count > 0)) {
805 notifyChange();
806 }
807 return count;
808 }
809
810 @Override
Wei Huang2914a7a2009-09-23 00:45:36 -0700811 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Wei Huang2914a7a2009-09-23 00:45:36 -0700812 int match = sURLMatcher.match(uri);
813
814 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Tom Taylor8168ff82013-02-19 14:30:23 -0800815 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
Wei Huang2914a7a2009-09-23 00:45:36 -0700816 }
817
Tom Taylor8168ff82013-02-19 14:30:23 -0800818 if (match != MMS_PART_ID) {
819 return null;
Wei Huang2914a7a2009-09-23 00:45:36 -0700820 }
821
Tom Taylor8168ff82013-02-19 14:30:23 -0800822 // Verify that the _data path points to mms data
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800823 return safeOpenFileHelper(uri, mode);
824 }
825
826 @NonNull
827 private ParcelFileDescriptor safeOpenFileHelper(
828 @NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
Tom Taylor8168ff82013-02-19 14:30:23 -0800829 Cursor c = query(uri, new String[]{"_data"}, null, null, null);
830 int count = (c != null) ? c.getCount() : 0;
831 if (count != 1) {
832 // If there is not exactly one result, throw an appropriate
833 // exception.
834 if (c != null) {
835 c.close();
836 }
837 if (count == 0) {
838 throw new FileNotFoundException("No entry for " + uri);
839 }
840 throw new FileNotFoundException("Multiple items at " + uri);
841 }
842
843 c.moveToFirst();
844 int i = c.getColumnIndex("_data");
845 String path = (i >= 0 ? c.getString(i) : null);
846 c.close();
847
848 if (path == null) {
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800849 throw new FileNotFoundException("Column _data not found.");
Tom Taylor8168ff82013-02-19 14:30:23 -0800850 }
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800851 File filePath = new File(path);
Tom Taylor8168ff82013-02-19 14:30:23 -0800852 try {
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800853 // The MmsProvider shouldn't open a file that isn't MMS data, so we verify that the
854 // _data path actually points to MMS data. That safeguards ourselves from callers who
855 // inserted or updated a URI (more specifically the _data column) with disallowed paths.
856 // TODO(afurtado): provide a more robust mechanism to avoid disallowed _data paths to
857 // be inserted/updated in the first place, including via SQL injection.
Tom Taylor8168ff82013-02-19 14:30:23 -0800858 if (!filePath.getCanonicalPath()
Ye Wenfdf4a822015-01-06 13:08:53 -0800859 .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getPath())) {
860 Log.e(TAG, "openFile: path "
861 + filePath.getCanonicalPath()
862 + " does not start with "
863 + getContext().getDir(PARTS_DIR_NAME, 0).getPath());
864 // Don't care return value
Tom Taylor8168ff82013-02-19 14:30:23 -0800865 return null;
866 }
867 } catch (IOException e) {
Ye Wenfdf4a822015-01-06 13:08:53 -0800868 Log.e(TAG, "openFile: create path failed " + e, e);
Tom Taylor8168ff82013-02-19 14:30:23 -0800869 return null;
870 }
871
Teow Wan Yee592ad6d2016-09-20 16:05:14 +0800872 int modeBits = ParcelFileDescriptor.parseMode(mode);
873 return ParcelFileDescriptor.open(filePath, modeBits);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800874 }
875
876 private void filterUnsupportedKeys(ContentValues values) {
877 // Some columns are unsupported. They should therefore
878 // neither be inserted nor updated. Filter them out.
879 values.remove(Mms.DELIVERY_TIME_TOKEN);
880 values.remove(Mms.SENDER_VISIBILITY);
881 values.remove(Mms.REPLY_CHARGING);
882 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
883 values.remove(Mms.REPLY_CHARGING_DEADLINE);
884 values.remove(Mms.REPLY_CHARGING_ID);
885 values.remove(Mms.REPLY_CHARGING_SIZE);
886 values.remove(Mms.PREVIOUSLY_SENT_BY);
887 values.remove(Mms.PREVIOUSLY_SENT_DATE);
888 values.remove(Mms.STORE);
889 values.remove(Mms.MM_STATE);
890 values.remove(Mms.MM_FLAGS_TOKEN);
891 values.remove(Mms.MM_FLAGS);
892 values.remove(Mms.STORE_STATUS);
893 values.remove(Mms.STORE_STATUS_TEXT);
894 values.remove(Mms.STORED);
895 values.remove(Mms.TOTALS);
896 values.remove(Mms.MBOX_TOTALS);
897 values.remove(Mms.MBOX_TOTALS_TOKEN);
898 values.remove(Mms.QUOTAS);
899 values.remove(Mms.MBOX_QUOTAS);
900 values.remove(Mms.MBOX_QUOTAS_TOKEN);
901 values.remove(Mms.MESSAGE_COUNT);
902 values.remove(Mms.START);
903 values.remove(Mms.DISTRIBUTION_INDICATOR);
904 values.remove(Mms.ELEMENT_DESCRIPTOR);
905 values.remove(Mms.LIMIT);
906 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
907 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
908 values.remove(Mms.STATUS_TEXT);
909 values.remove(Mms.APPLIC_ID);
910 values.remove(Mms.REPLY_APPLIC_ID);
911 values.remove(Mms.AUX_APPLIC_ID);
912 values.remove(Mms.DRM_CONTENT);
913 values.remove(Mms.ADAPTATION_ALLOWED);
914 values.remove(Mms.REPLACE_ID);
915 values.remove(Mms.CANCEL_ID);
916 values.remove(Mms.CANCEL_STATUS);
917
918 // Keys shouldn't be inserted or updated.
919 values.remove(Mms._ID);
920 }
921
922 private void notifyChange() {
923 getContext().getContentResolver().notifyChange(
Amith Yamasani43f9fb22014-09-10 15:56:47 -0700924 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800925 }
926
927 private final static String TAG = "MmsProvider";
928 private final static String VND_ANDROID_MMS = "vnd.android/mms";
929 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
930 private final static boolean DEBUG = false;
Joe Onoratodc946792011-04-07 18:41:15 -0700931 private final static boolean LOCAL_LOGV = false;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800932
933 private static final int MMS_ALL = 0;
934 private static final int MMS_ALL_ID = 1;
935 private static final int MMS_INBOX = 2;
936 private static final int MMS_INBOX_ID = 3;
937 private static final int MMS_SENT = 4;
938 private static final int MMS_SENT_ID = 5;
939 private static final int MMS_DRAFTS = 6;
940 private static final int MMS_DRAFTS_ID = 7;
941 private static final int MMS_OUTBOX = 8;
942 private static final int MMS_OUTBOX_ID = 9;
943 private static final int MMS_ALL_PART = 10;
944 private static final int MMS_MSG_PART = 11;
945 private static final int MMS_PART_ID = 12;
946 private static final int MMS_MSG_ADDR = 13;
947 private static final int MMS_SENDING_RATE = 14;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700948 private static final int MMS_REPORT_STATUS = 15;
949 private static final int MMS_REPORT_REQUEST = 16;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800950 private static final int MMS_DRM_STORAGE = 17;
951 private static final int MMS_DRM_STORAGE_ID = 18;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700952 private static final int MMS_THREADS = 19;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700953 private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800954
955 private static final UriMatcher
956 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
957
958 static {
959 sURLMatcher.addURI("mms", null, MMS_ALL);
960 sURLMatcher.addURI("mms", "#", MMS_ALL_ID);
961 sURLMatcher.addURI("mms", "inbox", MMS_INBOX);
962 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID);
963 sURLMatcher.addURI("mms", "sent", MMS_SENT);
964 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID);
965 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS);
966 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
967 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX);
968 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
969 sURLMatcher.addURI("mms", "part", MMS_ALL_PART);
970 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART);
971 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
972 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR);
973 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE);
974 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS);
975 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
976 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE);
977 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
Tom Taylor6c0ef242009-06-01 12:05:00 -0700978 sURLMatcher.addURI("mms", "threads", MMS_THREADS);
Tom Taylorc2db47d2012-03-27 15:15:39 -0700979 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800980 }
981
982 private SQLiteOpenHelper mOpenHelper;
983
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800984 private static String concatSelections(String selection1, String selection2) {
985 if (TextUtils.isEmpty(selection1)) {
986 return selection2;
987 } else if (TextUtils.isEmpty(selection2)) {
988 return selection1;
989 } else {
990 return selection1 + " AND " + selection2;
991 }
992 }
993}
994