• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.providers.telephony;
18 
19 import android.annotation.NonNull;
20 import android.app.AppOpsManager;
21 import android.content.BroadcastReceiver;
22 import android.content.ContentProvider;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.UriMatcher;
29 import android.database.Cursor;
30 import android.database.DatabaseUtils;
31 import android.database.MatrixCursor;
32 import android.database.sqlite.SQLiteDatabase;
33 import android.database.sqlite.SQLiteException;
34 import android.database.sqlite.SQLiteOpenHelper;
35 import android.database.sqlite.SQLiteQueryBuilder;
36 import android.net.Uri;
37 import android.os.Binder;
38 import android.os.ParcelFileDescriptor;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.provider.BaseColumns;
42 import android.provider.Telephony;
43 import android.provider.Telephony.CanonicalAddressesColumns;
44 import android.provider.Telephony.Mms;
45 import android.provider.Telephony.Mms.Addr;
46 import android.provider.Telephony.Mms.Part;
47 import android.provider.Telephony.Mms.Rate;
48 import android.provider.Telephony.MmsSms;
49 import android.provider.Telephony.Threads;
50 import android.system.ErrnoException;
51 import android.system.Os;
52 import android.telephony.SmsManager;
53 import android.telephony.SubscriptionManager;
54 import android.text.TextUtils;
55 import android.util.EventLog;
56 import android.util.Log;
57 
58 import com.android.internal.annotations.VisibleForTesting;
59 import com.android.internal.telephony.TelephonyPermissions;
60 import com.android.internal.telephony.util.TelephonyUtils;
61 
62 import com.google.android.mms.pdu.PduHeaders;
63 import com.google.android.mms.util.DownloadDrmHelper;
64 
65 import java.io.File;
66 import java.io.FileNotFoundException;
67 import java.io.IOException;
68 
69 /**
70  * The class to provide base facility to access MMS related content,
71  * which is stored in a SQLite database and in the file system.
72  */
73 public class MmsProvider extends ContentProvider {
74     static final String TABLE_PDU  = "pdu";
75     static final String TABLE_ADDR = "addr";
76     static final String TABLE_PART = "part";
77     static final String TABLE_RATE = "rate";
78     static final String TABLE_DRM  = "drm";
79     static final String TABLE_WORDS = "words";
80     static final String VIEW_PDU_RESTRICTED = "pdu_restricted";
81 
82     // The name of parts directory. The full dir is "app_parts".
83     static final String PARTS_DIR_NAME = "parts";
84 
85     private ProviderUtilWrapper providerUtilWrapper = new ProviderUtilWrapper();
86 
87     @VisibleForTesting
setProviderUtilWrapper(ProviderUtilWrapper providerUtilWrapper)88     public void setProviderUtilWrapper(ProviderUtilWrapper providerUtilWrapper) {
89         this.providerUtilWrapper = providerUtilWrapper;
90     }
91 
92     @Override
onCreate()93     public boolean onCreate() {
94         setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
95         mOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext());
96         TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext());
97 
98         // Creating intent broadcast receiver for user actions like Intent.ACTION_USER_REMOVED,
99         // where we would need to remove MMS related to removed user.
100         IntentFilter userIntentFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
101         getContext().registerReceiver(mUserIntentReceiver, userIntentFilter,
102                 Context.RECEIVER_NOT_EXPORTED);
103 
104         return true;
105     }
106 
107     // wrapper class to allow easier mocking of the static ProviderUtil in tests
108     @VisibleForTesting
109     public static class ProviderUtilWrapper {
isAccessRestricted(Context context, String packageName, int uid)110         public boolean isAccessRestricted(Context context, String packageName, int uid) {
111             return ProviderUtil.isAccessRestricted(context, packageName, uid);
112         }
113     }
114 
115     /**
116      * Return the proper view of "pdu" table for the current access status.
117      *
118      * @param accessRestricted If the access is restricted
119      * @return the table/view name of the mms data
120      */
getPduTable(boolean accessRestricted)121     public static String getPduTable(boolean accessRestricted) {
122         return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU;
123     }
124 
125     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)126     public Cursor query(Uri uri, String[] projection,
127             String selection, String[] selectionArgs, String sortOrder) {
128         final int callerUid = Binder.getCallingUid();
129         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
130         // First check if a restricted view of the "pdu" table should be used based on the
131         // caller's identity. Only system, phone or the default sms app can have full access
132         // of mms data. For other apps, we present a restricted view which only contains sent
133         // or received messages, without wap pushes.
134         final boolean accessRestricted = ProviderUtil.isAccessRestricted(
135                 getContext(), getCallingPackage(), callerUid);
136 
137         // If access is restricted, we don't allow subqueries in the query.
138         Log.v(TAG, "accessRestricted=" + accessRestricted);
139         if (accessRestricted) {
140             SqlQueryChecker.checkQueryParametersForSubqueries(projection, selection, sortOrder);
141         }
142 
143         final String pduTable = getPduTable(accessRestricted);
144         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
145 
146         // Generate the body of the query.
147         int match = sURLMatcher.match(uri);
148         if (LOCAL_LOGV) {
149             Log.v(TAG, "Query uri=" + uri + ", match=" + match);
150         }
151 
152         switch (match) {
153             case MMS_ALL:
154                 constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL, pduTable);
155                 break;
156             case MMS_INBOX:
157                 constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX, pduTable);
158                 break;
159             case MMS_SENT:
160                 constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT, pduTable);
161                 break;
162             case MMS_DRAFTS:
163                 constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS, pduTable);
164                 break;
165             case MMS_OUTBOX:
166                 constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX, pduTable);
167                 break;
168             case MMS_ALL_ID:
169                 qb.setTables(pduTable);
170                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
171                 break;
172             case MMS_INBOX_ID:
173             case MMS_SENT_ID:
174             case MMS_DRAFTS_ID:
175             case MMS_OUTBOX_ID:
176                 qb.setTables(pduTable);
177                 qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
178                 qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
179                         + getMessageBoxByMatch(match));
180                 break;
181             case MMS_ALL_PART:
182                 qb.setTables(TABLE_PART);
183                 break;
184             case MMS_MSG_PART:
185                 qb.setTables(TABLE_PART);
186                 qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
187                 break;
188             case MMS_PART_ID:
189                 qb.setTables(TABLE_PART);
190                 qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
191                 break;
192             case MMS_MSG_ADDR:
193                 qb.setTables(TABLE_ADDR);
194                 qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
195                 break;
196             case MMS_REPORT_STATUS:
197                 /*
198                    SELECT DISTINCT address,
199                                    T.delivery_status AS delivery_status,
200                                    T.read_status AS read_status
201                    FROM addr
202                    INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
203                                       ifnull(P2.st, 0) AS delivery_status,
204                                       ifnull(P3.read_status, 0) AS read_status
205                                FROM pdu P1
206                                INNER JOIN pdu P2
207                                ON P1.m_id = P2.m_id AND P2.m_type = 134
208                                LEFT JOIN pdu P3
209                                ON P1.m_id = P3.m_id AND P3.m_type = 136
210                                UNION
211                                SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
212                                       ifnull(P2.st, 0) AS delivery_status,
213                                       ifnull(P3.read_status, 0) AS read_status
214                                FROM pdu P1
215                                INNER JOIN pdu P3
216                                ON P1.m_id = P3.m_id AND P3.m_type = 136
217                                LEFT JOIN pdu P2
218                                ON P1.m_id = P2.m_id AND P2.m_type = 134) T
219                    ON (msg_id = id2 AND type = 151)
220                    OR (msg_id = id3 AND type = 137)
221                    WHERE T.id1 = ?;
222                  */
223                 qb.setTables(TABLE_ADDR + " INNER JOIN "
224                         + "(SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
225                         + "ifnull(P2.st, 0) AS delivery_status, "
226                         + "ifnull(P3.read_status, 0) AS read_status "
227                         + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P2 "
228                         + "ON P1.m_id=P2.m_id AND P2.m_type=134 "
229                         + "LEFT JOIN " + pduTable + " P3 "
230                         + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
231                         + "UNION "
232                         + "SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3, "
233                         + "ifnull(P2.st, 0) AS delivery_status, "
234                         + "ifnull(P3.read_status, 0) AS read_status "
235                         + "FROM " + pduTable + " P1 INNER JOIN " + pduTable + " P3 "
236                         + "ON P1.m_id=P3.m_id AND P3.m_type=136 "
237                         + "LEFT JOIN " + pduTable + " P2 "
238                         + "ON P1.m_id=P2.m_id AND P2.m_type=134) T "
239                         + "ON (msg_id=id2 AND type=151) OR (msg_id=id3 AND type=137)");
240                 qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
241                 qb.setDistinct(true);
242                 break;
243             case MMS_REPORT_REQUEST:
244                 /*
245                    SELECT address, d_rpt, rr
246                    FROM addr join pdu on pdu._id = addr.msg_id
247                    WHERE pdu._id = messageId AND addr.type = 151
248                  */
249                 qb.setTables(TABLE_ADDR + " join " +
250                         pduTable + " on " + pduTable + "._id = addr.msg_id");
251                 qb.appendWhere(pduTable + "._id = " + uri.getLastPathSegment());
252                 qb.appendWhere(" AND " + TABLE_ADDR + ".type = " + PduHeaders.TO);
253                 break;
254             case MMS_SENDING_RATE:
255                 qb.setTables(TABLE_RATE);
256                 break;
257             case MMS_DRM_STORAGE_ID:
258                 qb.setTables(TABLE_DRM);
259                 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
260                 break;
261             case MMS_THREADS:
262                 qb.setTables(pduTable + " group by thread_id");
263                 break;
264             default:
265                 Log.e(TAG, "query: invalid request: " + uri);
266                 return null;
267         }
268 
269         String selectionBySubIds;
270         final long token = Binder.clearCallingIdentity();
271         try {
272             // Filter MMS based on subId.
273             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
274                     callerUserHandle);
275         } finally {
276             Binder.restoreCallingIdentity(token);
277         }
278         if (selectionBySubIds == null) {
279             // No subscriptions associated with user, return empty cursor.
280             return new MatrixCursor((projection == null) ? (new String[] {}) : projection);
281         }
282         selection = DatabaseUtils.concatenateWhere(selection, selectionBySubIds);
283 
284         String finalSortOrder = null;
285         if (TextUtils.isEmpty(sortOrder)) {
286             if (qb.getTables().equals(pduTable)) {
287                 finalSortOrder = Mms.DATE + " DESC";
288             } else if (qb.getTables().equals(TABLE_PART)) {
289                 finalSortOrder = Part.SEQ;
290             }
291         } else {
292             finalSortOrder = sortOrder;
293         }
294 
295         Cursor ret;
296         try {
297             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
298             ret = qb.query(db, projection, selection,
299                     selectionArgs, null, null, finalSortOrder);
300         } catch (SQLiteException e) {
301             Log.e(TAG, "returning NULL cursor, query: " + uri, e);
302             return null;
303         }
304 
305         // TODO: Does this need to be a URI for this provider.
306         ret.setNotificationUri(getContext().getContentResolver(), uri);
307         return ret;
308     }
309 
constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable)310     private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox, String pduTable) {
311         qb.setTables(pduTable);
312 
313         if (msgBox != Mms.MESSAGE_BOX_ALL) {
314             qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
315         }
316     }
317 
318     @Override
getType(Uri uri)319     public String getType(Uri uri) {
320         int match = sURLMatcher.match(uri);
321         switch (match) {
322             case MMS_ALL:
323             case MMS_INBOX:
324             case MMS_SENT:
325             case MMS_DRAFTS:
326             case MMS_OUTBOX:
327                 return VND_ANDROID_DIR_MMS;
328             case MMS_ALL_ID:
329             case MMS_INBOX_ID:
330             case MMS_SENT_ID:
331             case MMS_DRAFTS_ID:
332             case MMS_OUTBOX_ID:
333                 return VND_ANDROID_MMS;
334             case MMS_PART_ID: {
335                 Cursor cursor = mOpenHelper.getReadableDatabase().query(
336                         TABLE_PART, new String[] { Part.CONTENT_TYPE },
337                         Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
338                         null, null, null);
339                 if (cursor != null) {
340                     try {
341                         if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
342                             return cursor.getString(0);
343                         } else {
344                             Log.e(TAG, "cursor.count() != 1: " + uri);
345                         }
346                     } finally {
347                         cursor.close();
348                     }
349                 } else {
350                     Log.e(TAG, "cursor == null: " + uri);
351                 }
352                 return "*/*";
353             }
354             case MMS_ALL_PART:
355             case MMS_MSG_PART:
356             case MMS_MSG_ADDR:
357             default:
358                 return "*/*";
359         }
360     }
361 
362     @Override
insert(Uri uri, ContentValues values)363     public Uri insert(Uri uri, ContentValues values) {
364         final int callerUid = Binder.getCallingUid();
365         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
366         final String callerPkg = getCallingPackage();
367         int msgBox = Mms.MESSAGE_BOX_ALL;
368         boolean notify = true;
369 
370         boolean forceNoNotify = values.containsKey(TelephonyBackupAgent.NOTIFY)
371                 && !values.getAsBoolean(TelephonyBackupAgent.NOTIFY);
372         values.remove(TelephonyBackupAgent.NOTIFY);
373         // check isAccessRestricted to prevent third parties from setting NOTIFY = false maliciously
374         if (forceNoNotify && !providerUtilWrapper.isAccessRestricted(
375                 getContext(), getCallingPackage(), callerUid)) {
376             notify = false;
377         }
378 
379         int match = sURLMatcher.match(uri);
380         if (LOCAL_LOGV) {
381             Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
382         }
383 
384         String table = TABLE_PDU;
385         switch (match) {
386             case MMS_ALL:
387                 Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX);
388                 if (msgBoxObj != null) {
389                     msgBox = (Integer) msgBoxObj;
390                 }
391                 else {
392                     // default to inbox
393                     msgBox = Mms.MESSAGE_BOX_INBOX;
394                 }
395                 break;
396             case MMS_INBOX:
397                 msgBox = Mms.MESSAGE_BOX_INBOX;
398                 break;
399             case MMS_SENT:
400                 msgBox = Mms.MESSAGE_BOX_SENT;
401                 break;
402             case MMS_DRAFTS:
403                 msgBox = Mms.MESSAGE_BOX_DRAFTS;
404                 break;
405             case MMS_OUTBOX:
406                 msgBox = Mms.MESSAGE_BOX_OUTBOX;
407                 break;
408             case MMS_MSG_PART:
409                 notify = false;
410                 table = TABLE_PART;
411                 break;
412             case MMS_MSG_ADDR:
413                 notify = false;
414                 table = TABLE_ADDR;
415                 break;
416             case MMS_SENDING_RATE:
417                 notify = false;
418                 table = TABLE_RATE;
419                 break;
420             case MMS_DRM_STORAGE:
421                 notify = false;
422                 table = TABLE_DRM;
423                 break;
424             default:
425                 Log.e(TAG, "insert: invalid request: " + uri);
426                 return null;
427         }
428 
429         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
430         ContentValues finalValues;
431         Uri res = Mms.CONTENT_URI;
432         Uri caseSpecificUri = null;
433         long rowId;
434 
435         int subId;
436         if (values.containsKey(Telephony.Sms.SUBSCRIPTION_ID)) {
437             subId = values.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID);
438         } else {
439             // TODO (b/256992531): Currently, one sim card is set as default sms subId in work
440             //  profile. Default sms subId should be updated based on user pref.
441             subId = SmsManager.getDefaultSmsSubscriptionId();
442             if (SubscriptionManager.isValidSubscriptionId(subId)) {
443                 values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
444             }
445         }
446 
447         if (table.equals(TABLE_PDU)) {
448             if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(), subId,
449                     callerUserHandle)) {
450                 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(), subId,
451                         callerUid, callerPkg);
452                 return null;
453             }
454 
455             boolean addDate = !values.containsKey(Mms.DATE);
456             boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX);
457 
458             // Filter keys we don't support yet.
459             filterUnsupportedKeys(values);
460 
461             // TODO: Should initialValues be validated, e.g. if it
462             // missed some significant keys?
463             finalValues = new ContentValues(values);
464 
465             long timeInMillis = System.currentTimeMillis();
466 
467             if (addDate) {
468                 finalValues.put(Mms.DATE, timeInMillis / 1000L);
469             }
470 
471             if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) {
472                 finalValues.put(Mms.MESSAGE_BOX, msgBox);
473             }
474 
475             if (msgBox != Mms.MESSAGE_BOX_INBOX) {
476                 // Mark all non-inbox messages read.
477                 finalValues.put(Mms.READ, 1);
478             }
479 
480             // thread_id
481             Long threadId = values.getAsLong(Mms.THREAD_ID);
482             String address = values.getAsString(CanonicalAddressesColumns.ADDRESS);
483 
484             if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
485                 finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address));
486             }
487 
488             if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) {
489                 // Only SYSTEM or PHONE can set CREATOR
490                 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
491                 // set CREATOR using the truth on caller.
492                 // Note: Inferring package name from UID may include unrelated package names
493                 finalValues.put(Telephony.Mms.CREATOR, callerPkg);
494             }
495 
496             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
497                 Log.e(TAG, "MmsProvider.insert: failed!");
498                 return null;
499             }
500 
501             // Notify change when an MMS is received.
502             if (msgBox == Mms.MESSAGE_BOX_INBOX) {
503                 caseSpecificUri = ContentUris.withAppendedId(Mms.Inbox.CONTENT_URI, rowId);
504             }
505 
506             res = Uri.parse(res + "/" + rowId);
507         } else if (table.equals(TABLE_ADDR)) {
508             finalValues = new ContentValues(values);
509             finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0));
510 
511             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
512                 Log.e(TAG, "Failed to insert address");
513                 return null;
514             }
515 
516             res = Uri.parse(res + "/addr/" + rowId);
517         } else if (table.equals(TABLE_PART)) {
518             boolean containsDataPath = values != null && values.containsKey(Part._DATA);
519             finalValues = new ContentValues(values);
520 
521             if (match == MMS_MSG_PART) {
522                 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
523             }
524 
525             String contentType = values.getAsString("ct");
526 
527             // text/plain and app application/smil store their "data" inline in the
528             // table so there's no need to create the file
529             boolean plainText = false;
530             boolean smilText = false;
531             if ("text/plain".equals(contentType)) {
532                 if (containsDataPath) {
533                     Log.e(TAG, "insert: can't insert text/plain with _data");
534                     return null;
535                 }
536                 plainText = true;
537             } else if ("application/smil".equals(contentType)) {
538                 if (containsDataPath) {
539                     Log.e(TAG, "insert: can't insert application/smil with _data");
540                     return null;
541                 }
542                 smilText = true;
543             }
544             if (!plainText && !smilText) {
545                 String path;
546                 if (containsDataPath) {
547                     // The _data column is filled internally in MmsProvider or from the
548                     // TelephonyBackupAgent, so this check is just to avoid it from being
549                     // inadvertently set. This is not supposed to be a protection against malicious
550                     // attack, since sql injection could still be attempted to bypass the check.
551                     // On the other hand, the MmsProvider does verify that the _data column has an
552                     // allowed value before opening any uri/files.
553                     if (!"com.android.providers.telephony".equals(callerPkg)) {
554                         Log.e(TAG, "insert: can't insert _data");
555                         return null;
556                     }
557                     try {
558                         path = values.getAsString(Part._DATA);
559                         final String partsDirPath = getContext()
560                                 .getDir(PARTS_DIR_NAME, 0).getCanonicalPath();
561                         if (!new File(path).getCanonicalPath().startsWith(partsDirPath)) {
562                             Log.e(TAG, "insert: path "
563                                     + path
564                                     + " does not start with "
565                                     + partsDirPath);
566                             // Don't care return value
567                             return null;
568                         }
569                     } catch (IOException e) {
570                         Log.e(TAG, "insert part: create path failed " + e, e);
571                         return null;
572                     }
573                 } else {
574                     // Use the filename if possible, otherwise use the current time as the name.
575                     String contentLocation = values.getAsString("cl");
576                     if (!TextUtils.isEmpty(contentLocation)) {
577                         File f = new File(contentLocation);
578                         contentLocation = "_" + f.getName();
579                     } else {
580                         contentLocation = "";
581                     }
582 
583                     // Generate the '_data' field of the part with default
584                     // permission settings.
585                     path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
586                             + "/PART_" + System.currentTimeMillis() + contentLocation;
587 
588                     if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) {
589                         // Adds the .fl extension to the filename if contentType is
590                         // "application/vnd.oma.drm.message"
591                         path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path);
592                     }
593                 }
594 
595                 finalValues.put(Part._DATA, path);
596 
597                 File partFile = new File(path);
598                 if (!partFile.exists()) {
599                     try {
600                         if (!partFile.createNewFile()) {
601                             throw new IllegalStateException(
602                                     "Unable to create new partFile: " + path);
603                         }
604                         // Give everyone rw permission until we encrypt the file
605                         // (in PduPersister.persistData). Once the file is encrypted, the
606                         // permissions will be set to 0644.
607                         try {
608                             Os.chmod(path, 0666);
609                             if (LOCAL_LOGV) {
610                                 Log.d(TAG, "MmsProvider.insert chmod is successful");
611                             }
612                         } catch (ErrnoException e) {
613                             Log.e(TAG, "Exception in chmod: " + e);
614                         }
615                     } catch (IOException e) {
616                         Log.e(TAG, "createNewFile", e);
617                         throw new IllegalStateException(
618                                 "Unable to create new partFile: " + path);
619                     }
620                 }
621             }
622 
623             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
624                 Log.e(TAG, "MmsProvider.insert: failed!");
625                 return null;
626             }
627 
628             res = Uri.parse(res + "/part/" + rowId);
629 
630             // Don't use a trigger for updating the words table because of a bug
631             // in FTS3.  The bug is such that the call to get the last inserted
632             // row is incorrect.
633             if (plainText) {
634                 // Update the words table with a corresponding row.  The words table
635                 // allows us to search for words quickly, without scanning the whole
636                 // table;
637                 ContentValues cv = new ContentValues();
638 
639                 // we're using the row id of the part table row but we're also using ids
640                 // from the sms table so this divides the space into two large chunks.
641                 // The row ids from the part table start at 2 << 32.
642                 cv.put(Telephony.MmsSms.WordsTable.ID, (2L << 32) + rowId);
643                 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text"));
644                 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId);
645                 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2);
646                 cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, subId);
647                 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
648             }
649 
650         } else if (table.equals(TABLE_RATE)) {
651             long now = values.getAsLong(Rate.SENT_TIME);
652             long oneHourAgo = now - 1000 * 60 * 60;
653             // Delete all unused rows (time earlier than one hour ago).
654             db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null);
655             db.insert(table, null, values);
656         } else if (table.equals(TABLE_DRM)) {
657             String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath()
658                     + "/PART_" + System.currentTimeMillis();
659             finalValues = new ContentValues(1);
660             finalValues.put("_data", path);
661             finalValues.put("sub_id", subId);
662 
663             File partFile = new File(path);
664             if (!partFile.exists()) {
665                 try {
666                     if (!partFile.createNewFile()) {
667                         throw new IllegalStateException(
668                                 "Unable to create new file: " + path);
669                     }
670                 } catch (IOException e) {
671                     Log.e(TAG, "createNewFile", e);
672                     throw new IllegalStateException(
673                             "Unable to create new file: " + path);
674                 }
675             }
676 
677             if ((rowId = db.insert(table, null, finalValues)) <= 0) {
678                 Log.e(TAG, "MmsProvider.insert: failed!");
679                 return null;
680             }
681             res = Uri.parse(res + "/drm/" + rowId);
682         } else {
683             throw new AssertionError("Unknown table type: " + table);
684         }
685 
686         if (notify) {
687             notifyChange(res, caseSpecificUri);
688         }
689         return res;
690     }
691 
getMessageBoxByMatch(int match)692     private int getMessageBoxByMatch(int match) {
693         switch (match) {
694             case MMS_INBOX_ID:
695             case MMS_INBOX:
696                 return Mms.MESSAGE_BOX_INBOX;
697             case MMS_SENT_ID:
698             case MMS_SENT:
699                 return Mms.MESSAGE_BOX_SENT;
700             case MMS_DRAFTS_ID:
701             case MMS_DRAFTS:
702                 return Mms.MESSAGE_BOX_DRAFTS;
703             case MMS_OUTBOX_ID:
704             case MMS_OUTBOX:
705                 return Mms.MESSAGE_BOX_OUTBOX;
706             default:
707                 throw new IllegalArgumentException("bad Arg: " + match);
708         }
709     }
710 
711     @Override
delete(Uri uri, String selection, String[] selectionArgs)712     public int delete(Uri uri, String selection,
713             String[] selectionArgs) {
714         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
715         int match = sURLMatcher.match(uri);
716         if (LOCAL_LOGV) {
717             Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
718         }
719 
720         String table, extraSelection = null;
721         boolean notify = false;
722 
723         switch (match) {
724             case MMS_ALL_ID:
725             case MMS_INBOX_ID:
726             case MMS_SENT_ID:
727             case MMS_DRAFTS_ID:
728             case MMS_OUTBOX_ID:
729                 notify = true;
730                 table = TABLE_PDU;
731                 extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
732                 break;
733             case MMS_ALL:
734             case MMS_INBOX:
735             case MMS_SENT:
736             case MMS_DRAFTS:
737             case MMS_OUTBOX:
738                 notify = true;
739                 table = TABLE_PDU;
740                 if (match != MMS_ALL) {
741                     int msgBox = getMessageBoxByMatch(match);
742                     extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
743                 }
744                 break;
745             case MMS_ALL_PART:
746                 table = TABLE_PART;
747                 break;
748             case MMS_MSG_PART:
749                 table = TABLE_PART;
750                 extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
751                 break;
752             case MMS_PART_ID:
753                 table = TABLE_PART;
754                 extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
755                 break;
756             case MMS_MSG_ADDR:
757                 table = TABLE_ADDR;
758                 extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
759                 break;
760             case MMS_DRM_STORAGE:
761                 table = TABLE_DRM;
762                 break;
763             default:
764                 Log.w(TAG, "No match for URI '" + uri + "'");
765                 return 0;
766         }
767 
768         String finalSelection = concatSelections(selection, extraSelection);
769         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
770         int deletedRows = 0;
771 
772         final long token = Binder.clearCallingIdentity();
773         String selectionBySubIds;
774         try {
775             // Filter SMS based on subId.
776             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
777                     callerUserHandle);
778         } finally {
779             Binder.restoreCallingIdentity(token);
780         }
781         if (selectionBySubIds == null) {
782             // No subscriptions associated with user, return 0.
783             return 0;
784         }
785         finalSelection = DatabaseUtils.concatenateWhere(finalSelection, selectionBySubIds);
786 
787         if (TABLE_PDU.equals(table)) {
788             deletedRows = deleteMessages(getContext(), db, finalSelection,
789                                          selectionArgs, uri);
790         } else if (TABLE_PART.equals(table)) {
791             deletedRows = deleteParts(db, finalSelection, selectionArgs);
792         } else if (TABLE_DRM.equals(table)) {
793             deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
794         } else {
795             deletedRows = db.delete(table, finalSelection, selectionArgs);
796         }
797 
798         if ((deletedRows > 0) && notify) {
799             notifyChange(uri, null);
800         }
801         return deletedRows;
802     }
803 
deleteMessages(Context context, SQLiteDatabase db, String selection, String[] selectionArgs, Uri uri)804     static int deleteMessages(Context context, SQLiteDatabase db,
805             String selection, String[] selectionArgs, Uri uri) {
806         Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
807                 selection, selectionArgs, null, null, null);
808         if (cursor == null) {
809             return 0;
810         }
811 
812         try {
813             if (cursor.getCount() == 0) {
814                 return 0;
815             }
816 
817             while (cursor.moveToNext()) {
818                 deleteParts(db, Part.MSG_ID + " = ?",
819                         new String[] { String.valueOf(cursor.getLong(0)) });
820             }
821         } finally {
822             cursor.close();
823         }
824 
825         int count = db.delete(TABLE_PDU, selection, selectionArgs);
826         if (count > 0) {
827             Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
828             intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
829             if (LOCAL_LOGV) {
830                 Log.v(TAG, "Broadcasting intent: " + intent);
831             }
832             context.sendBroadcast(intent);
833         }
834         return count;
835     }
836 
deleteParts(SQLiteDatabase db, String selection, String[] selectionArgs)837     private static int deleteParts(SQLiteDatabase db, String selection,
838             String[] selectionArgs) {
839         return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
840     }
841 
deleteTempDrmData(SQLiteDatabase db, String selection, String[] selectionArgs)842     private static int deleteTempDrmData(SQLiteDatabase db, String selection,
843             String[] selectionArgs) {
844         return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
845     }
846 
deleteDataRows(SQLiteDatabase db, String table, String selection, String[] selectionArgs)847     private static int deleteDataRows(SQLiteDatabase db, String table,
848             String selection, String[] selectionArgs) {
849         Cursor cursor = db.query(table, new String[] { "_data" },
850                 selection, selectionArgs, null, null, null);
851         if (cursor == null) {
852             // FIXME: This might be an error, ignore it may cause
853             // unpredictable result.
854             return 0;
855         }
856 
857         try {
858             if (cursor.getCount() == 0) {
859                 return 0;
860             }
861 
862             while (cursor.moveToNext()) {
863                 try {
864                     // Delete the associated files saved on file-system.
865                     String path = cursor.getString(0);
866                     if (path != null) {
867                         new File(path).delete();
868                     }
869                 } catch (Throwable ex) {
870                     Log.e(TAG, ex.getMessage(), ex);
871                 }
872             }
873         } finally {
874             cursor.close();
875         }
876 
877         return db.delete(table, selection, selectionArgs);
878     }
879 
880     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)881     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
882         // The _data column is filled internally in MmsProvider, so this check is just to avoid
883         // it from being inadvertently set. This is not supposed to be a protection against
884         // malicious attack, since sql injection could still be attempted to bypass the check. On
885         // the other hand, the MmsProvider does verify that the _data column has an allowed value
886         // before opening any uri/files.
887         if (values != null && values.containsKey(Part._DATA)) {
888             return 0;
889         }
890         final int callerUid = Binder.getCallingUid();
891         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
892         final String callerPkg = getCallingPackage();
893         int match = sURLMatcher.match(uri);
894         if (LOCAL_LOGV) {
895             Log.v(TAG, "Update uri=" + uri + ", match=" + match);
896         }
897 
898         boolean notify = false;
899         String msgId = null;
900         String table;
901 
902         switch (match) {
903             case MMS_ALL_ID:
904             case MMS_INBOX_ID:
905             case MMS_SENT_ID:
906             case MMS_DRAFTS_ID:
907             case MMS_OUTBOX_ID:
908                 msgId = uri.getLastPathSegment();
909             // fall-through
910             case MMS_ALL:
911             case MMS_INBOX:
912             case MMS_SENT:
913             case MMS_DRAFTS:
914             case MMS_OUTBOX:
915                 notify = true;
916                 table = TABLE_PDU;
917                 break;
918 
919             case MMS_MSG_PART:
920             case MMS_PART_ID:
921                 table = TABLE_PART;
922                 break;
923 
924             case MMS_PART_RESET_FILE_PERMISSION:
925                 String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + '/' +
926                         uri.getPathSegments().get(1);
927 
928                 try {
929                     File canonicalFile = new File(path).getCanonicalFile();
930                     String partsDirPath = getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath();
931                     if (!canonicalFile.getPath().startsWith(partsDirPath + '/')) {
932                         EventLog.writeEvent(0x534e4554, "240685104",
933                                 callerUid, (TAG + " update: path " + path +
934                                         " does not start with " + partsDirPath));
935                         return 0;
936                     }
937                     // Reset the file permission back to read for everyone but me.
938                     Os.chmod(canonicalFile.getPath(), 0644);
939                     if (LOCAL_LOGV) {
940                         Log.d(TAG, "MmsProvider.update chmod is successful for path: " + path);
941                     }
942                 } catch (ErrnoException | IOException e) {
943                     Log.e(TAG, "Exception in chmod: " + e);
944                 }
945                 return 0;
946 
947             default:
948                 Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
949                 return 0;
950         }
951 
952         String extraSelection = null;
953         final long token = Binder.clearCallingIdentity();
954         String selectionBySubIds;
955         try {
956             // Filter MMS based on subId.
957             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
958                     callerUserHandle);
959         } finally {
960             Binder.restoreCallingIdentity(token);
961         }
962         if (selectionBySubIds == null) {
963             // No subscriptions associated with user, return 0.
964             return 0;
965         }
966         extraSelection = DatabaseUtils.concatenateWhere(extraSelection, selectionBySubIds);
967 
968         ContentValues finalValues;
969         if (table.equals(TABLE_PDU)) {
970             // Filter keys that we don't support yet.
971             filterUnsupportedKeys(values);
972             if (ProviderUtil.shouldRemoveCreator(values, callerUid)) {
973                 // CREATOR should not be changed by non-SYSTEM/PHONE apps
974                 Log.w(TAG, callerPkg + " tries to update CREATOR");
975                 values.remove(Mms.CREATOR);
976             }
977             finalValues = new ContentValues(values);
978 
979             if (msgId != null) {
980                 extraSelection = Mms._ID + "=" + msgId;
981             }
982         } else if (table.equals(TABLE_PART)) {
983             finalValues = new ContentValues(values);
984 
985             switch (match) {
986                 case MMS_MSG_PART:
987                     extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
988                     break;
989                 case MMS_PART_ID:
990                     extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
991                     break;
992                 default:
993                     break;
994             }
995         } else {
996             return 0;
997         }
998 
999         String finalSelection = concatSelections(selection, extraSelection);
1000         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1001         int count = db.update(table, finalValues, finalSelection, selectionArgs);
1002         if (notify && (count > 0)) {
1003             notifyChange(uri, null);
1004         }
1005         return count;
1006     }
1007 
1008     @Override
openFile(Uri uri, String mode)1009     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
1010         int match = sURLMatcher.match(uri);
1011 
1012         if (Log.isLoggable(TAG, Log.VERBOSE)) {
1013             Log.d(TAG, "openFile: uri=" + uri + ", mode=" + mode + ", match=" + match);
1014         }
1015 
1016         if (match != MMS_PART_ID) {
1017             return null;
1018         }
1019 
1020         return safeOpenFileHelper(uri, mode);
1021     }
1022 
1023     @NonNull
safeOpenFileHelper( @onNull Uri uri, @NonNull String mode)1024     private ParcelFileDescriptor safeOpenFileHelper(
1025             @NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
1026         Cursor c = query(uri, new String[]{"_data"}, null, null, null);
1027         int count = (c != null) ? c.getCount() : 0;
1028         if (count != 1) {
1029             // If there is not exactly one result, throw an appropriate
1030             // exception.
1031             if (c != null) {
1032                 c.close();
1033             }
1034             if (count == 0) {
1035                 throw new FileNotFoundException("No entry for " + uri);
1036             }
1037             throw new FileNotFoundException("Multiple items at " + uri);
1038         }
1039 
1040         c.moveToFirst();
1041         int i = c.getColumnIndex("_data");
1042         String path = (i >= 0 ? c.getString(i) : null);
1043         c.close();
1044 
1045         if (path == null) {
1046             throw new FileNotFoundException("Column _data not found.");
1047         }
1048 
1049         File filePath = new File(path);
1050         try {
1051             // The MmsProvider shouldn't open a file that isn't MMS data, so we verify that the
1052             // _data path actually points to MMS data. That safeguards ourselves from callers who
1053             // inserted or updated a URI (more specifically the _data column) with disallowed paths.
1054             // TODO(afurtado): provide a more robust mechanism to avoid disallowed _data paths to
1055             // be inserted/updated in the first place, including via SQL injection.
1056             if (!filePath.getCanonicalPath()
1057                     .startsWith(getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath())) {
1058                 Log.e(TAG, "openFile: path "
1059                         + filePath.getCanonicalPath()
1060                         + " does not start with "
1061                         + getContext().getDir(PARTS_DIR_NAME, 0).getCanonicalPath());
1062                 // Don't care return value
1063                 return null;
1064             }
1065         } catch (IOException e) {
1066             Log.e(TAG, "openFile: create path failed " + e, e);
1067             return null;
1068         }
1069 
1070         int modeBits = ParcelFileDescriptor.parseMode(mode);
1071         return ParcelFileDescriptor.open(filePath, modeBits);
1072     }
1073 
filterUnsupportedKeys(ContentValues values)1074     private void filterUnsupportedKeys(ContentValues values) {
1075         // Some columns are unsupported.  They should therefore
1076         // neither be inserted nor updated.  Filter them out.
1077         values.remove(Mms.DELIVERY_TIME_TOKEN);
1078         values.remove(Mms.SENDER_VISIBILITY);
1079         values.remove(Mms.REPLY_CHARGING);
1080         values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
1081         values.remove(Mms.REPLY_CHARGING_DEADLINE);
1082         values.remove(Mms.REPLY_CHARGING_ID);
1083         values.remove(Mms.REPLY_CHARGING_SIZE);
1084         values.remove(Mms.PREVIOUSLY_SENT_BY);
1085         values.remove(Mms.PREVIOUSLY_SENT_DATE);
1086         values.remove(Mms.STORE);
1087         values.remove(Mms.MM_STATE);
1088         values.remove(Mms.MM_FLAGS_TOKEN);
1089         values.remove(Mms.MM_FLAGS);
1090         values.remove(Mms.STORE_STATUS);
1091         values.remove(Mms.STORE_STATUS_TEXT);
1092         values.remove(Mms.STORED);
1093         values.remove(Mms.TOTALS);
1094         values.remove(Mms.MBOX_TOTALS);
1095         values.remove(Mms.MBOX_TOTALS_TOKEN);
1096         values.remove(Mms.QUOTAS);
1097         values.remove(Mms.MBOX_QUOTAS);
1098         values.remove(Mms.MBOX_QUOTAS_TOKEN);
1099         values.remove(Mms.MESSAGE_COUNT);
1100         values.remove(Mms.START);
1101         values.remove(Mms.DISTRIBUTION_INDICATOR);
1102         values.remove(Mms.ELEMENT_DESCRIPTOR);
1103         values.remove(Mms.LIMIT);
1104         values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
1105         values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
1106         values.remove(Mms.STATUS_TEXT);
1107         values.remove(Mms.APPLIC_ID);
1108         values.remove(Mms.REPLY_APPLIC_ID);
1109         values.remove(Mms.AUX_APPLIC_ID);
1110         values.remove(Mms.DRM_CONTENT);
1111         values.remove(Mms.ADAPTATION_ALLOWED);
1112         values.remove(Mms.REPLACE_ID);
1113         values.remove(Mms.CANCEL_ID);
1114         values.remove(Mms.CANCEL_STATUS);
1115 
1116         // Keys shouldn't be inserted or updated.
1117         values.remove(Mms._ID);
1118     }
1119 
notifyChange(final Uri uri, final Uri caseSpecificUri)1120     private void notifyChange(final Uri uri, final Uri caseSpecificUri) {
1121         final Context context = getContext();
1122         if (caseSpecificUri != null) {
1123             context.getContentResolver().notifyChange(
1124                 caseSpecificUri, null, true, UserHandle.USER_ALL);
1125         }
1126         context.getContentResolver().notifyChange(
1127                 MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
1128         ProviderUtil.notifyIfNotDefaultSmsApp(caseSpecificUri == null ? uri : caseSpecificUri,
1129                 getCallingPackage(), context);
1130     }
1131 
1132     private final static String TAG = "MmsProvider";
1133     private final static String VND_ANDROID_MMS = "vnd.android/mms";
1134     private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
1135     private final static boolean DEBUG = false;
1136     private final static boolean LOCAL_LOGV = false;
1137 
1138     private static final int MMS_ALL                      = 0;
1139     private static final int MMS_ALL_ID                   = 1;
1140     private static final int MMS_INBOX                    = 2;
1141     private static final int MMS_INBOX_ID                 = 3;
1142     private static final int MMS_SENT                     = 4;
1143     private static final int MMS_SENT_ID                  = 5;
1144     private static final int MMS_DRAFTS                   = 6;
1145     private static final int MMS_DRAFTS_ID                = 7;
1146     private static final int MMS_OUTBOX                   = 8;
1147     private static final int MMS_OUTBOX_ID                = 9;
1148     private static final int MMS_ALL_PART                 = 10;
1149     private static final int MMS_MSG_PART                 = 11;
1150     private static final int MMS_PART_ID                  = 12;
1151     private static final int MMS_MSG_ADDR                 = 13;
1152     private static final int MMS_SENDING_RATE             = 14;
1153     private static final int MMS_REPORT_STATUS            = 15;
1154     private static final int MMS_REPORT_REQUEST           = 16;
1155     private static final int MMS_DRM_STORAGE              = 17;
1156     private static final int MMS_DRM_STORAGE_ID           = 18;
1157     private static final int MMS_THREADS                  = 19;
1158     private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
1159 
1160     private static final UriMatcher
1161             sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
1162 
1163     static {
1164         sURLMatcher.addURI("mms", null,         MMS_ALL);
1165         sURLMatcher.addURI("mms", "#",          MMS_ALL_ID);
1166         sURLMatcher.addURI("mms", "inbox",      MMS_INBOX);
1167         sURLMatcher.addURI("mms", "inbox/#",    MMS_INBOX_ID);
1168         sURLMatcher.addURI("mms", "sent",       MMS_SENT);
1169         sURLMatcher.addURI("mms", "sent/#",     MMS_SENT_ID);
1170         sURLMatcher.addURI("mms", "drafts",     MMS_DRAFTS);
1171         sURLMatcher.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
1172         sURLMatcher.addURI("mms", "outbox",     MMS_OUTBOX);
1173         sURLMatcher.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
1174         sURLMatcher.addURI("mms", "part",       MMS_ALL_PART);
1175         sURLMatcher.addURI("mms", "#/part",     MMS_MSG_PART);
1176         sURLMatcher.addURI("mms", "part/#",     MMS_PART_ID);
1177         sURLMatcher.addURI("mms", "#/addr",     MMS_MSG_ADDR);
1178         sURLMatcher.addURI("mms", "rate",       MMS_SENDING_RATE);
1179         sURLMatcher.addURI("mms", "report-status/#",  MMS_REPORT_STATUS);
1180         sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
1181         sURLMatcher.addURI("mms", "drm",        MMS_DRM_STORAGE);
1182         sURLMatcher.addURI("mms", "drm/#",      MMS_DRM_STORAGE_ID);
1183         sURLMatcher.addURI("mms", "threads",    MMS_THREADS);
1184         sURLMatcher.addURI("mms", "resetFilePerm/*",    MMS_PART_RESET_FILE_PERMISSION);
1185     }
1186 
1187     @VisibleForTesting
1188     public SQLiteOpenHelper mOpenHelper;
1189 
concatSelections(String selection1, String selection2)1190     private static String concatSelections(String selection1, String selection2) {
1191         if (TextUtils.isEmpty(selection1)) {
1192             return selection2;
1193         } else if (TextUtils.isEmpty(selection2)) {
1194             return selection1;
1195         } else {
1196             return selection1 + " AND " + selection2;
1197         }
1198     }
1199 
1200     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
1201         @Override
1202         public void onReceive(Context context, Intent intent) {
1203             switch (intent.getAction()) {
1204                 case Intent.ACTION_USER_REMOVED:
1205                     UserHandle userToBeRemoved  = intent.getParcelableExtra(Intent.EXTRA_USER,
1206                             UserHandle.class);
1207                     UserManager userManager = context.getSystemService(UserManager.class);
1208                     if ((userToBeRemoved == null) || (userManager == null) ||
1209                             (!userManager.isManagedProfile(userToBeRemoved.getIdentifier()))) {
1210                         // Do not delete MMS if removed profile is not managed profile.
1211                         return;
1212                     }
1213                     Log.d(TAG, "Received ACTION_USER_REMOVED for managed profile - Deleting MMS.");
1214 
1215                     // Deleting MMS related to managed profile.
1216                     Uri uri = Telephony.Mms.CONTENT_URI;
1217                     SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1218 
1219                     final long token = Binder.clearCallingIdentity();
1220                     String selectionBySubIds;
1221                     try {
1222                         // Filter MMS based on subId.
1223                         selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
1224                                 userToBeRemoved);
1225                     } finally {
1226                         Binder.restoreCallingIdentity(token);
1227                     }
1228                     if (selectionBySubIds == null) {
1229                         // No subscriptions associated with user, return.
1230                         return;
1231                     }
1232 
1233                     int deletedRows = deleteMessages(getContext(), db, selectionBySubIds,
1234                             null, uri);
1235                     if (deletedRows > 0) {
1236                         // Don't update threads unless something changed.
1237                         MmsSmsDatabaseHelper.updateThreads(db, selectionBySubIds, null);
1238                         notifyChange(uri, null);
1239                     }
1240                     break;
1241             }
1242         }
1243     };
1244 }
1245