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