blob: 710fa438cb8bf34d067d553864e97bd4bdb72546 [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 Wene07acb92014-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 Wene07acb92014-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";
Ye Wen72f13552015-03-10 14:17:13 -070065 static final String VIEW_PDU_RESTRICTED = "pdu_restricted";
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080066
Ye Weneaa93e62015-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() {
Ye Wenb2ce2d32014-07-28 14:49:30 -070072 setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
73 mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080074 return true;
75 }
76
Ye Wen72f13552015-03-10 14:17:13 -070077 /**
78 * Return the proper view of "pdu" table for the current access status.
79 *
80 * @param accessRestricted If the access is restricted
81 * @return the table/view name of the mms data
82 */
83 public static String getPduTable(boolean accessRestricted) {
84 return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU;
85 }
86
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080087 @Override
88 public Cursor query(Uri uri, String[] projection,
89 String selection, String[] selectionArgs, String sortOrder) {
Ye Wen72f13552015-03-10 14:17:13 -070090 // First check if a restricted view of the "pdu" table should be used based on the
91 // caller's identity. Only system, phone or the default sms app can have full access
92 // of mms data. For other apps, we present a restricted view which only contains sent
93 // or received messages, without wap pushes.
94 final boolean accessRestricted = ProviderUtil.isAccessRestricted(
95 getContext(), getCallingPackage(), Binder.getCallingUid());
96 final String pduTable = getPduTable(accessRestricted);
97
The Android Open Source Project7236c3a2009-03-03 19:32:44 -080098 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
99
100 // Generate the body of the query.
101 int match = sURLMatcher.match(uri);
102 if (LOCAL_LOGV) {
103 Log.v(TAG, "Query uri=" + uri + ", match=" + match);
104 }
105
106 switch (match) {
107 case MMS_ALL:
Ye Wen72f13552015-03-10 14:17:13 -0700108 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800109 break;
110 case MMS_INBOX:
Ye Wen72f13552015-03-10 14:17:13 -0700111 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800112 break;
113 case MMS_SENT:
Ye Wen72f13552015-03-10 14:17:13 -0700114 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800115 break;
116 case MMS_DRAFTS:
Ye Wen72f13552015-03-10 14:17:13 -0700117 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800118 break;
119 case MMS_OUTBOX:
Ye Wen72f13552015-03-10 14:17:13 -0700120 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX, pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800121 break;
122 case MMS_ALL_ID:
Ye Wen72f13552015-03-10 14:17:13 -0700123 qb.setTables(pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800124 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
125 break;
126 case MMS_INBOX_ID:
127 case MMS_SENT_ID:
128 case MMS_DRAFTS_ID:
129 case MMS_OUTBOX_ID:
Ye Wen72f13552015-03-10 14:17:13 -0700130 qb.setTables(pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800131 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
132 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
133 + getMessageBoxByMatch(match));
134 break;
135 case MMS_ALL_PART:
136 qb.setTables(TABLE_PART);
137 break;
138 case MMS_MSG_PART:
139 qb.setTables(TABLE_PART);
140 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
141 break;
142 case MMS_PART_ID:
143 qb.setTables(TABLE_PART);
144 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
145 break;
146 case MMS_MSG_ADDR:
147 qb.setTables(TABLE_ADDR);
148 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
149 break;
150 case MMS_REPORT_STATUS:
151 /*
152 SELECT DISTINCT address,
153 T.delivery_status AS delivery_status,
154 T.read_status AS read_status
155 FROM addr
156 INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
157 ifnull(P2.st, 0) AS delivery_status,
158 ifnull(P3.read_status, 0) AS read_status
159 FROM pdu P1
160 INNER JOIN pdu P2
161 ON P1.m_id = P2.m_id AND P2.m_type = 134
162 LEFT JOIN pdu P3
163 ON P1.m_id = P3.m_id AND P3.m_type = 136
164 UNION
165 SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
166 ifnull(P2.st, 0) AS delivery_status,
167 ifnull(P3.read_status, 0) AS read_status
168 FROM pdu P1
169 INNER JOIN pdu P3
170 ON P1.m_id = P3.m_id AND P3.m_type = 136
171 LEFT JOIN pdu P2
172 ON P1.m_id = P2.m_id AND P2.m_type = 134) T
173 ON (msg_id = id2 AND type = 151)
174 OR (msg_id = id3 AND type = 137)
175 WHERE T.id1 = ?;
176 */
Ye Wen72f13552015-03-10 14:17:13 -0700177 qb.setTables(TABLE_ADDR + " INNER JOIN "
178 + "(SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
179 + "ifnull(P2.st, 0) AS delivery_status, "
180 + "ifnull(P3.read_status, 0) AS read_status "
181 + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P2 "
182 + "ON P1.m_id=P2.m_id AND P2.m_type=134 "
183 + "LEFT JOIN " + pduTable + " P3 "
184 + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
185 + "UNION "
186 + "SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
187 + "ifnull(P2.st, 0) AS delivery_status, "
188 + "ifnull(P3.read_status, 0) AS read_status "
189 + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P3 "
190 + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
191 + "LEFT JOIN " + pduTable + " P2 "
192 + "ON P1.m_id=P2.m_id AND P2.m_type=134) T "
193 + "ON (msg_id=id2 AND type=151) OR (msg_id=id3 AND type=137)");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800194 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
195 qb.setDistinct(true);
196 break;
197 case MMS_REPORT_REQUEST:
198 /*
199 SELECT address, d_rpt, rr
200 FROM addr join pdu on pdu._id = addr.msg_id
201 WHERE pdu._id = messageId AND addr.type = 151
202 */
203 qb.setTables(TABLE_ADDR + " join " +
Ye Wen72f13552015-03-10 14:17:13 -0700204 pduTable + " on " + pduTable + "._id = addr.msg_id");
205 qb.appendWhere(pduTable + "._id = " + uri.getLastPathSegment());
206 qb.appendWhere(" AND " + TABLE_ADDR + ".type = " + PduHeaders.TO);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800207 break;
208 case MMS_SENDING_RATE:
209 qb.setTables(TABLE_RATE);
210 break;
211 case MMS_DRM_STORAGE_ID:
212 qb.setTables(TABLE_DRM);
213 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
214 break;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700215 case MMS_THREADS:
Ye Wen72f13552015-03-10 14:17:13 -0700216 qb.setTables(pduTable + " group by thread_id");
Tom Taylor6c0ef242009-06-01 12:05:00 -0700217 break;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800218 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700219 Log.e(TAG, "query: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800220 return null;
221 }
222
223 String finalSortOrder = null;
224 if (TextUtils.isEmpty(sortOrder)) {
Ye Wen72f13552015-03-10 14:17:13 -0700225 if (qb.getTables().equals(pduTable)) {
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800226 finalSortOrder = Mms.DATE + " DESC";
227 } else if (qb.getTables().equals(TABLE_PART)) {
228 finalSortOrder = Part.SEQ;
229 }
230 } else {
231 finalSortOrder = sortOrder;
232 }
233
Tom Taylor3ad9da42014-01-16 13:57:11 -0800234 Cursor ret;
235 try {
236 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
237 ret = qb.query(db, projection, selection,
238 selectionArgs, null, null, finalSortOrder);
239 } catch (SQLiteException e) {
240 Log.e(TAG, "returning NULL cursor, query: " + uri, e);
241 return null;
242 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800243
244 // TODO: Does this need to be a URI for this provider.
245 ret.setNotificationUri(getContext().getContentResolver(), uri);
246 return ret;
247 }
248
Ye Wen72f13552015-03-10 14:17:13 -0700249 private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable) {
250 qb.setTables(pduTable);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800251
252 if (msgBox != Mms.MESSAGE_BOX_ALL) {
253 qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
254 }
255 }
256
257 @Override
258 public String getType(Uri uri) {
259 int match = sURLMatcher.match(uri);
260 switch (match) {
261 case MMS_ALL:
262 case MMS_INBOX:
263 case MMS_SENT:
264 case MMS_DRAFTS:
265 case MMS_OUTBOX:
266 return VND_ANDROID_DIR_MMS;
267 case MMS_ALL_ID:
268 case MMS_INBOX_ID:
269 case MMS_SENT_ID:
270 case MMS_DRAFTS_ID:
271 case MMS_OUTBOX_ID:
272 return VND_ANDROID_MMS;
273 case MMS_PART_ID: {
274 Cursor cursor = mOpenHelper.getReadableDatabase().query(
275 TABLE_PART, new String[] { Part.CONTENT_TYPE },
276 Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
277 null, null, null);
278 if (cursor != null) {
279 try {
280 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
281 return cursor.getString(0);
282 } else {
283 Log.e(TAG, "cursor.count() != 1: " + uri);
284 }
285 } finally {
286 cursor.close();
287 }
288 } else {
289 Log.e(TAG, "cursor == null: " + uri);
290 }
291 return "*/*";
292 }
293 case MMS_ALL_PART:
294 case MMS_MSG_PART:
295 case MMS_MSG_ADDR:
296 default:
297 return "*/*";
298 }
299 }
300
301 @Override
302 public Uri insert(Uri uri, ContentValues values) {
Tom Taylor438403e2013-02-21 16:56:01 -0800303 // Don't let anyone insert anything with the _data column
304 if (values != null && values.containsKey(Part._DATA)) {
305 return null;
306 }
Ye Wene07acb92014-11-19 12:06:05 -0800307 final int callerUid = Binder.getCallingUid();
Ye Wen72f13552015-03-10 14:17:13 -0700308 final String callerPkg = getCallingPackage();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800309 int msgBox = Mms.MESSAGE_BOX_ALL;
310 boolean notify = true;
311
312 int match = sURLMatcher.match(uri);
313 if (LOCAL_LOGV) {
314 Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
315 }
316
317 String table = TABLE_PDU;
318 switch (match) {
319 case MMS_ALL:
320 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
321 if (msgBoxObj != null) {
322 msgBox = (Integer) msgBoxObj;
323 }
324 else {
325 // default to inbox
326 msgBox = Mms.MESSAGE_BOX_INBOX;
327 }
328 break;
329 case MMS_INBOX:
330 msgBox = Mms.MESSAGE_BOX_INBOX;
331 break;
332 case MMS_SENT:
333 msgBox = Mms.MESSAGE_BOX_SENT;
334 break;
335 case MMS_DRAFTS:
336 msgBox = Mms.MESSAGE_BOX_DRAFTS;
337 break;
338 case MMS_OUTBOX:
339 msgBox = Mms.MESSAGE_BOX_OUTBOX;
340 break;
341 case MMS_MSG_PART:
342 notify = false;
343 table = TABLE_PART;
344 break;
345 case MMS_MSG_ADDR:
346 notify = false;
347 table = TABLE_ADDR;
348 break;
349 case MMS_SENDING_RATE:
350 notify = false;
351 table = TABLE_RATE;
352 break;
353 case MMS_DRM_STORAGE:
354 notify = false;
355 table = TABLE_DRM;
356 break;
357 default:
Wei Huang2914a7a2009-09-23 00:45:36 -0700358 Log.e(TAG, "insert: invalid request: " + uri);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800359 return null;
360 }
361
362 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
363 ContentValues finalValues;
364 Uri res = Mms.CONTENT_URI;
365 long rowId;
366
367 if (table.equals(TABLE_PDU)) {
368 boolean addDate = !values.containsKey(Mms.DATE);
369 boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
370
371 // Filter keys we don't support yet.
372 filterUnsupportedKeys(values);
373
374 // TODO: Should initialValues be validated, e.g. if it
375 // missed some significant keys?
376 finalValues = new ContentValues(values);
377
378 long timeInMillis = System.currentTimeMillis();
379
380 if (addDate) {
381 finalValues.put(Mms.DATE, timeInMillis / 1000L);
382 }
383
384 if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
385 finalValues.put(Mms.MESSAGE_BOX, msgBox);
386 }
387
388 if (msgBox != Mms.MESSAGE_BOX_INBOX) {
389 // Mark all non-inbox messages read.
390 finalValues.put(Mms.READ, 1);
391 }
392
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700393 // thread_id
394 Long threadId = values.getAsLong(Mms.THREAD_ID);
395 String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
396
Tom Taylor59269962012-05-09 14:42:35 -0700397 if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
Yusuf T. Mobile21c25bc2009-06-16 13:40:38 -0700398 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
399 }
400
Ye Wene07acb92014-11-19 12:06:05 -0800401 if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
402 // Only SYSTEM or PHONE can set CREATOR
403 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
404 // set CREATOR using the truth on caller.
405 // Note: Inferring package name from UID may include unrelated package names
Ye Wen72f13552015-03-10 14:17:13 -0700406 finalValues.put(Telephony.Mms.CREATOR, callerPkg);
Ye Wene07acb92014-11-19 12:06:05 -0800407 }
408
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800409 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wene07acb92014-11-19 12:06:05 -0800410 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800411 return null;
412 }
413
414 res = Uri.parse(res + "/" + rowId);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800415 } else if (table.equals(TABLE_ADDR)) {
416 finalValues = new ContentValues(values);
417 finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
418
419 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wene07acb92014-11-19 12:06:05 -0800420 Log.e(TAG, "Failed to insert address");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800421 return null;
422 }
423
424 res = Uri.parse(res + "/addr/" + rowId);
425 } else if (table.equals(TABLE_PART)) {
426 finalValues = new ContentValues(values);
427
428 if (match == MMS_MSG_PART) {
429 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
430 }
431
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700432 String contentType = values.getAsString("ct");
Mark Wagner8e5ee782010-01-04 17:39:06 -0800433
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700434 // text/plain and app application/smil store their "data" inline in the
435 // table so there's no need to create the file
Tom Taylorc2db47d2012-03-27 15:15:39 -0700436 boolean plainText = false;
437 boolean smilText = false;
438 if ("text/plain".equals(contentType)) {
439 plainText = true;
440 } else if ("application/smil".equals(contentType)) {
441 smilText = true;
442 }
Mark Wagner8e5ee782010-01-04 17:39:06 -0800443 if (!plainText && !smilText) {
Tom Taylorc2db47d2012-03-27 15:15:39 -0700444 // Use the filename if possible, otherwise use the current time as the name.
445 String contentLocation = values.getAsString("cl");
446 if (!TextUtils.isEmpty(contentLocation)) {
447 File f = new File(contentLocation);
448 contentLocation = "_" + f.getName();
449 } else {
450 contentLocation = "";
451 }
452
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700453 // Generate the '_data' field of the part with default
454 // permission settings.
Ye Weneaa93e62015-01-06 13:08:53 -0800455 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
Tom Taylorc2db47d2012-03-27 15:15:39 -0700456 + "/PART_" + System.currentTimeMillis() + contentLocation;
457
458 if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
459 // Adds the .fl extension to the filename if contentType is
460 // "application/vnd.oma.drm.message"
461 path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
462 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800463
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700464 finalValues.put(Part._DATA, path);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800465
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700466 File partFile = new File(path);
467 if (!partFile.exists()) {
468 try {
469 if (!partFile.createNewFile()) {
470 throw new IllegalStateException(
471 "Unable to create new partFile: " + path);
472 }
Tom Taylorc2db47d2012-03-27 15:15:39 -0700473 // Give everyone rw permission until we encrypt the file
474 // (in PduPersister.persistData). Once the file is encrypted, the
475 // permissions will be set to 0644.
476 int result = FileUtils.setPermissions(path, 0666, -1, -1);
477 if (LOCAL_LOGV) {
478 Log.d(TAG, "MmsProvider.insert setPermissions result: " + result);
479 }
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700480 } catch (IOException e) {
481 Log.e(TAG, "createNewFile", e);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800482 throw new IllegalStateException(
483 "Unable to create new partFile: " + path);
484 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800485 }
486 }
487
488 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wene07acb92014-11-19 12:06:05 -0800489 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800490 return null;
491 }
492
493 res = Uri.parse(res + "/part/" + rowId);
Mark Wagner8e5ee782010-01-04 17:39:06 -0800494
495 // Don't use a trigger for updating the words table because of a bug
496 // in FTS3. The bug is such that the call to get the last inserted
497 // row is incorrect.
498 if (plainText) {
499 // Update the words table with a corresponding row. The words table
500 // allows us to search for words quickly, without scanning the whole
501 // table;
502 ContentValues cv = new ContentValues();
503
504 // we're using the row id of the part table row but we're also using ids
505 // from the sms table so this divides the space into two large chunks.
506 // The row ids from the part table start at 2 << 32.
507 cv.put(Telephony.MmsSms.WordsTable.ID, (2 << 32) + rowId);
508 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
509 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
510 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
511 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
512 }
513
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800514 } else if (table.equals(TABLE_RATE)) {
515 long now = values.getAsLong(Rate.SENT_TIME);
516 long oneHourAgo = now - 1000 * 60 * 60;
517 // Delete all unused rows (time earlier than one hour ago).
518 db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
519 db.insert(table, null, values);
520 } else if (table.equals(TABLE_DRM)) {
Ye Weneaa93e62015-01-06 13:08:53 -0800521 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800522 + "/PART_" + System.currentTimeMillis();
523 finalValues = new ContentValues(1);
524 finalValues.put("_data", path);
525
526 File partFile = new File(path);
527 if (!partFile.exists()) {
528 try {
529 if (!partFile.createNewFile()) {
530 throw new IllegalStateException(
531 "Unable to create new file: " + path);
532 }
533 } catch (IOException e) {
534 Log.e(TAG, "createNewFile", e);
535 throw new IllegalStateException(
536 "Unable to create new file: " + path);
537 }
538 }
539
540 if ((rowId = db.insert(table, null, finalValues)) <= 0) {
Ye Wene07acb92014-11-19 12:06:05 -0800541 Log.e(TAG, "MmsProvider.insert: failed!");
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800542 return null;
543 }
544 res = Uri.parse(res + "/drm/" + rowId);
545 } else {
546 throw new AssertionError("Unknown table type: " + table);
547 }
548
549 if (notify) {
550 notifyChange();
551 }
552 return res;
553 }
554
555 private int getMessageBoxByMatch(int match) {
556 switch (match) {
557 case MMS_INBOX_ID:
558 case MMS_INBOX:
559 return Mms.MESSAGE_BOX_INBOX;
560 case MMS_SENT_ID:
561 case MMS_SENT:
562 return Mms.MESSAGE_BOX_SENT;
563 case MMS_DRAFTS_ID:
564 case MMS_DRAFTS:
565 return Mms.MESSAGE_BOX_DRAFTS;
566 case MMS_OUTBOX_ID:
567 case MMS_OUTBOX:
568 return Mms.MESSAGE_BOX_OUTBOX;
569 default:
570 throw new IllegalArgumentException("bad Arg: " + match);
571 }
572 }
573
574 @Override
575 public int delete(Uri uri, String selection,
576 String[] selectionArgs) {
577 int match = sURLMatcher.match(uri);
578 if (LOCAL_LOGV) {
579 Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
580 }
581
582 String table, extraSelection = null;
583 boolean notify = false;
584
585 switch (match) {
586 case MMS_ALL_ID:
587 case MMS_INBOX_ID:
588 case MMS_SENT_ID:
589 case MMS_DRAFTS_ID:
590 case MMS_OUTBOX_ID:
591 notify = true;
592 table = TABLE_PDU;
593 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
594 break;
595 case MMS_ALL:
596 case MMS_INBOX:
597 case MMS_SENT:
598 case MMS_DRAFTS:
599 case MMS_OUTBOX:
600 notify = true;
601 table = TABLE_PDU;
602 if (match != MMS_ALL) {
603 int msgBox = getMessageBoxByMatch(match);
604 extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
605 }
606 break;
607 case MMS_ALL_PART:
608 table = TABLE_PART;
609 break;
610 case MMS_MSG_PART:
611 table = TABLE_PART;
612 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
613 break;
614 case MMS_PART_ID:
615 table = TABLE_PART;
616 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
617 break;
618 case MMS_MSG_ADDR:
619 table = TABLE_ADDR;
620 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
621 break;
622 case MMS_DRM_STORAGE:
623 table = TABLE_DRM;
624 break;
625 default:
626 Log.w(TAG, "No match for URI '" + uri + "'");
627 return 0;
628 }
629
630 String finalSelection = concatSelections(selection, extraSelection);
631 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
632 int deletedRows = 0;
633
634 if (TABLE_PDU.equals(table)) {
635 deletedRows = deleteMessages(getContext(), db, finalSelection,
636 selectionArgs, uri);
637 } else if (TABLE_PART.equals(table)) {
638 deletedRows = deleteParts(db, finalSelection, selectionArgs);
639 } else if (TABLE_DRM.equals(table)) {
640 deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
641 } else {
642 deletedRows = db.delete(table, finalSelection, selectionArgs);
643 }
644
645 if ((deletedRows > 0) && notify) {
646 notifyChange();
647 }
648 return deletedRows;
649 }
650
651 static int deleteMessages(Context context, SQLiteDatabase db,
652 String selection, String[] selectionArgs, Uri uri) {
653 Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
654 selection, selectionArgs, null, null, null);
655 if (cursor == null) {
656 return 0;
657 }
658
659 try {
660 if (cursor.getCount() == 0) {
661 return 0;
662 }
663
664 while (cursor.moveToNext()) {
665 deleteParts(db, Part.MSG_ID + " = ?",
666 new String[] { String.valueOf(cursor.getLong(0)) });
667 }
668 } finally {
669 cursor.close();
670 }
671
672 int count = db.delete(TABLE_PDU, selection, selectionArgs);
673 if (count > 0) {
674 Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
675 intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
676 if (LOCAL_LOGV) {
677 Log.v(TAG, "Broadcasting intent: " + intent);
678 }
679 context.sendBroadcast(intent);
680 }
681 return count;
682 }
683
684 private static int deleteParts(SQLiteDatabase db, String selection,
685 String[] selectionArgs) {
686 return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
687 }
688
689 private static int deleteTempDrmData(SQLiteDatabase db, String selection,
690 String[] selectionArgs) {
691 return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
692 }
693
694 private static int deleteDataRows(SQLiteDatabase db, String table,
695 String selection, String[] selectionArgs) {
696 Cursor cursor = db.query(table, new String[] { "_data" },
697 selection, selectionArgs, null, null, null);
698 if (cursor == null) {
699 // FIXME: This might be an error, ignore it may cause
700 // unpredictable result.
701 return 0;
702 }
703
704 try {
705 if (cursor.getCount() == 0) {
706 return 0;
707 }
708
709 while (cursor.moveToNext()) {
710 try {
711 // Delete the associated files saved on file-system.
Mark Wagnerf0a9e902009-06-19 16:00:13 -0700712 String path = cursor.getString(0);
713 if (path != null) {
714 new File(path).delete();
715 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800716 } catch (Throwable ex) {
717 Log.e(TAG, ex.getMessage(), ex);
718 }
719 }
720 } finally {
721 cursor.close();
722 }
723
724 return db.delete(table, selection, selectionArgs);
725 }
726
727 @Override
728 public int update(Uri uri, ContentValues values,
729 String selection, String[] selectionArgs) {
Tom Taylor438403e2013-02-21 16:56:01 -0800730 // Don't let anyone update the _data column
731 if (values != null && values.containsKey(Part._DATA)) {
732 return 0;
733 }
Ye Wene07acb92014-11-19 12:06:05 -0800734 final int callerUid = Binder.getCallingUid();
Ye Wen72f13552015-03-10 14:17:13 -0700735 final String callerPkg = getCallingPackage();
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800736 int match = sURLMatcher.match(uri);
737 if (LOCAL_LOGV) {
738 Log.v(TAG, "Update uri=" + uri + ", match=" + match);
739 }
740
741 boolean notify = false;
742 String msgId = null;
743 String table;
744
745 switch (match) {
746 case MMS_ALL_ID:
747 case MMS_INBOX_ID:
748 case MMS_SENT_ID:
749 case MMS_DRAFTS_ID:
750 case MMS_OUTBOX_ID:
751 msgId = uri.getLastPathSegment();
752 // fall-through
753 case MMS_ALL:
754 case MMS_INBOX:
755 case MMS_SENT:
756 case MMS_DRAFTS:
757 case MMS_OUTBOX:
758 notify = true;
759 table = TABLE_PDU;
760 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700761
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800762 case MMS_MSG_PART:
763 case MMS_PART_ID:
764 table = TABLE_PART;
765 break;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700766
767 case MMS_PART_RESET_FILE_PERMISSION:
Ye Weneaa93e62015-01-06 13:08:53 -0800768 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
Tom Taylorc2db47d2012-03-27 15:15:39 -0700769 uri.getPathSegments().get(1);
770 // Reset the file permission back to read for everyone but me.
771 int result = FileUtils.setPermissions(path, 0644, -1, -1);
772 if (LOCAL_LOGV) {
773 Log.d(TAG, "MmsProvider.update setPermissions result: " + result +
774 " for path: " + path);
775 }
776 return 0;
777
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800778 default:
779 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
780 return 0;
781 }
782
783 String extraSelection = null;
784 ContentValues finalValues;
785 if (table.equals(TABLE_PDU)) {
786 // Filter keys that we don't support yet.
787 filterUnsupportedKeys(values);
Ye Wene07acb92014-11-19 12:06:05 -0800788 if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
789 // CREATOR should not be changed by non-SYSTEM/PHONE apps
Ye Wen72f13552015-03-10 14:17:13 -0700790 Log.w(TAG, callerPkg + " tries to update CREATOR");
Ye Wene07acb92014-11-19 12:06:05 -0800791 values.remove(Mms.CREATOR);
792 }
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800793 finalValues = new ContentValues(values);
794
795 if (msgId != null) {
796 extraSelection = Mms._ID + "=" + msgId;
797 }
798 } else if (table.equals(TABLE_PART)) {
799 finalValues = new ContentValues(values);
800
801 switch (match) {
802 case MMS_MSG_PART:
803 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
804 break;
805 case MMS_PART_ID:
806 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
807 break;
808 default:
809 break;
810 }
811 } else {
812 return 0;
813 }
814
815 String finalSelection = concatSelections(selection, extraSelection);
816 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
817 int count = db.update(table, finalValues, finalSelection, selectionArgs);
818 if (notify && (count > 0)) {
819 notifyChange();
820 }
821 return count;
822 }
823
824 @Override
Wei Huang2914a7a2009-09-23 00:45:36 -0700825 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Wei Huang2914a7a2009-09-23 00:45:36 -0700826 int match = sURLMatcher.match(uri);
827
828 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Tom Taylor8168ff82013-02-19 14:30:23 -0800829 Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
Wei Huang2914a7a2009-09-23 00:45:36 -0700830 }
831
Tom Taylor8168ff82013-02-19 14:30:23 -0800832 if (match != MMS_PART_ID) {
833 return null;
Wei Huang2914a7a2009-09-23 00:45:36 -0700834 }
835
Tom Taylor8168ff82013-02-19 14:30:23 -0800836 // Verify that the _data path points to mms data
837 Cursor c = query(uri, new String[]{"_data"}, null, null, null);
838 int count = (c != null) ? c.getCount() : 0;
839 if (count != 1) {
840 // If there is not exactly one result, throw an appropriate
841 // exception.
842 if (c != null) {
843 c.close();
844 }
845 if (count == 0) {
846 throw new FileNotFoundException("No entry for " + uri);
847 }
848 throw new FileNotFoundException("Multiple items at " + uri);
849 }
850
851 c.moveToFirst();
852 int i = c.getColumnIndex("_data");
853 String path = (i >= 0 ? c.getString(i) : null);
854 c.close();
855
856 if (path == null) {
857 return null;
858 }
859 try {
860 File filePath = new File(path);
861 if (!filePath.getCanonicalPath()
Ye Weneaa93e62015-01-06 13:08:53 -0800862 .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getPath())) {
863 Log.e(TAG, "openFile: path "
864 + filePath.getCanonicalPath()
865 + " does not start with "
866 + getContext().getDir(PARTS_DIR_NAME, 0).getPath());
867 // Don't care return value
868 filePath.delete();
Tom Taylor8168ff82013-02-19 14:30:23 -0800869 return null;
870 }
871 } catch (IOException e) {
Ye Weneaa93e62015-01-06 13:08:53 -0800872 Log.e(TAG, "openFile: create path failed " + e, e);
Tom Taylor8168ff82013-02-19 14:30:23 -0800873 return null;
874 }
875
876 return openFileHelper(uri, mode);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800877 }
878
879 private void filterUnsupportedKeys(ContentValues values) {
880 // Some columns are unsupported. They should therefore
881 // neither be inserted nor updated. Filter them out.
882 values.remove(Mms.DELIVERY_TIME_TOKEN);
883 values.remove(Mms.SENDER_VISIBILITY);
884 values.remove(Mms.REPLY_CHARGING);
885 values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
886 values.remove(Mms.REPLY_CHARGING_DEADLINE);
887 values.remove(Mms.REPLY_CHARGING_ID);
888 values.remove(Mms.REPLY_CHARGING_SIZE);
889 values.remove(Mms.PREVIOUSLY_SENT_BY);
890 values.remove(Mms.PREVIOUSLY_SENT_DATE);
891 values.remove(Mms.STORE);
892 values.remove(Mms.MM_STATE);
893 values.remove(Mms.MM_FLAGS_TOKEN);
894 values.remove(Mms.MM_FLAGS);
895 values.remove(Mms.STORE_STATUS);
896 values.remove(Mms.STORE_STATUS_TEXT);
897 values.remove(Mms.STORED);
898 values.remove(Mms.TOTALS);
899 values.remove(Mms.MBOX_TOTALS);
900 values.remove(Mms.MBOX_TOTALS_TOKEN);
901 values.remove(Mms.QUOTAS);
902 values.remove(Mms.MBOX_QUOTAS);
903 values.remove(Mms.MBOX_QUOTAS_TOKEN);
904 values.remove(Mms.MESSAGE_COUNT);
905 values.remove(Mms.START);
906 values.remove(Mms.DISTRIBUTION_INDICATOR);
907 values.remove(Mms.ELEMENT_DESCRIPTOR);
908 values.remove(Mms.LIMIT);
909 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
910 values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
911 values.remove(Mms.STATUS_TEXT);
912 values.remove(Mms.APPLIC_ID);
913 values.remove(Mms.REPLY_APPLIC_ID);
914 values.remove(Mms.AUX_APPLIC_ID);
915 values.remove(Mms.DRM_CONTENT);
916 values.remove(Mms.ADAPTATION_ALLOWED);
917 values.remove(Mms.REPLACE_ID);
918 values.remove(Mms.CANCEL_ID);
919 values.remove(Mms.CANCEL_STATUS);
920
921 // Keys shouldn't be inserted or updated.
922 values.remove(Mms._ID);
923 }
924
925 private void notifyChange() {
926 getContext().getContentResolver().notifyChange(
Amith Yamasani43f9fb22014-09-10 15:56:47 -0700927 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800928 }
929
930 private final static String TAG = "MmsProvider";
931 private final static String VND_ANDROID_MMS = "vnd.android/mms";
932 private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
933 private final static boolean DEBUG = false;
Joe Onoratodc946792011-04-07 18:41:15 -0700934 private final static boolean LOCAL_LOGV = false;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800935
936 private static final int MMS_ALL = 0;
937 private static final int MMS_ALL_ID = 1;
938 private static final int MMS_INBOX = 2;
939 private static final int MMS_INBOX_ID = 3;
940 private static final int MMS_SENT = 4;
941 private static final int MMS_SENT_ID = 5;
942 private static final int MMS_DRAFTS = 6;
943 private static final int MMS_DRAFTS_ID = 7;
944 private static final int MMS_OUTBOX = 8;
945 private static final int MMS_OUTBOX_ID = 9;
946 private static final int MMS_ALL_PART = 10;
947 private static final int MMS_MSG_PART = 11;
948 private static final int MMS_PART_ID = 12;
949 private static final int MMS_MSG_ADDR = 13;
950 private static final int MMS_SENDING_RATE = 14;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700951 private static final int MMS_REPORT_STATUS = 15;
952 private static final int MMS_REPORT_REQUEST = 16;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800953 private static final int MMS_DRM_STORAGE = 17;
954 private static final int MMS_DRM_STORAGE_ID = 18;
Tom Taylor6c0ef242009-06-01 12:05:00 -0700955 private static final int MMS_THREADS = 19;
Tom Taylorc2db47d2012-03-27 15:15:39 -0700956 private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800957
958 private static final UriMatcher
959 sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
960
961 static {
962 sURLMatcher.addURI("mms", null, MMS_ALL);
963 sURLMatcher.addURI("mms", "#", MMS_ALL_ID);
964 sURLMatcher.addURI("mms", "inbox", MMS_INBOX);
965 sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID);
966 sURLMatcher.addURI("mms", "sent", MMS_SENT);
967 sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID);
968 sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS);
969 sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
970 sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX);
971 sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
972 sURLMatcher.addURI("mms", "part", MMS_ALL_PART);
973 sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART);
974 sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
975 sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR);
976 sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE);
977 sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS);
978 sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
979 sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE);
980 sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
Tom Taylor6c0ef242009-06-01 12:05:00 -0700981 sURLMatcher.addURI("mms", "threads", MMS_THREADS);
Tom Taylorc2db47d2012-03-27 15:15:39 -0700982 sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION);
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800983 }
984
985 private SQLiteOpenHelper mOpenHelper;
986
The Android Open Source Project7236c3a2009-03-03 19:32:44 -0800987 private static String concatSelections(String selection1, String selection2) {
988 if (TextUtils.isEmpty(selection1)) {
989 return selection2;
990 } else if (TextUtils.isEmpty(selection2)) {
991 return selection1;
992 } else {
993 return selection1 + " AND " + selection2;
994 }
995 }
996}
997