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