1 /*
2  * Copyright (C) 2006 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.ContentResolver;
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.content.pm.PackageManager;
30 import android.database.Cursor;
31 import android.database.DatabaseUtils;
32 import android.database.MatrixCursor;
33 import android.database.sqlite.SQLiteDatabase;
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.UserHandle;
39 import android.os.UserManager;
40 import android.provider.Contacts;
41 import android.provider.Telephony;
42 import android.provider.Telephony.MmsSms;
43 import android.provider.Telephony.Sms;
44 import android.provider.Telephony.Threads;
45 import android.telephony.SmsManager;
46 import android.telephony.SmsMessage;
47 import android.telephony.SubscriptionManager;
48 import android.text.TextUtils;
49 import android.util.Log;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.telephony.TelephonyPermissions;
53 import com.android.internal.telephony.util.TelephonyUtils;
54 
55 import java.util.HashMap;
56 import java.util.List;
57 public class SmsProvider extends ContentProvider {
58     /* No response constant from SmsResponse */
59     static final int NO_ERROR_CODE = -1;
60 
61     private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
62     private static final Uri ICC_URI = Uri.parse("content://sms/icc");
63     private static final Uri ICC_SUBID_URI = Uri.parse("content://sms/icc_subId");
64     static final String TABLE_SMS = "sms";
65     static final String TABLE_RAW = "raw";
66     static final String TABLE_ATTACHMENTS = "attachments";
67     static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
68     static final String TABLE_SR_PENDING = "sr_pending";
69     private static final String TABLE_WORDS = "words";
70     static final String VIEW_SMS_RESTRICTED = "sms_restricted";
71 
72     private static final Integer ONE = Integer.valueOf(1);
73 
74     private static final String[] CONTACT_QUERY_PROJECTION =
75             new String[] { Contacts.Phones.PERSON_ID };
76     private static final int PERSON_ID_COLUMN = 0;
77 
78     /** Delete any raw messages or message segments marked deleted that are older than an hour. */
79     static final long RAW_MESSAGE_EXPIRE_AGE_MS = (long) (60 * 60 * 1000);
80 
81     /**
82      * These are the columns that are available when reading SMS
83      * messages from the ICC.  Columns whose names begin with "is_"
84      * have either "true" or "false" as their values.
85      */
86     private final static String[] ICC_COLUMNS = new String[] {
87         // N.B.: These columns must appear in the same order as the
88         // calls to add appear in convertIccToSms.
89         "service_center_address",       // getServiceCenterAddress
90         "address",                      // getDisplayOriginatingAddress or getRecipientAddress
91         "message_class",                // getMessageClass
92         "body",                         // getDisplayMessageBody
93         "date",                         // getTimestampMillis
94         "status",                       // getStatusOnIcc
95         "index_on_icc",                 // getIndexOnIcc (1-based index)
96         "is_status_report",             // isStatusReportMessage
97         "transport_type",               // Always "sms".
98         "type",                         // depend on getStatusOnIcc
99         "locked",                       // Always 0 (false).
100         "error_code",                   // Always -1 (NO_ERROR_CODE), previously it was 0 always.
101         "_id"
102     };
103 
104     @Override
onCreate()105     public boolean onCreate() {
106         setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
107         // So we have two database files. One in de, one in ce. Here only "raw" table is in
108         // mDeOpenHelper, other tables are all in mCeOpenHelper.
109         mDeOpenHelper = MmsSmsDatabaseHelper.getInstanceForDe(getContext());
110         mCeOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext());
111         TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext());
112 
113         // Creating intent broadcast receiver for user actions like Intent.ACTION_USER_REMOVED,
114         // where we would need to remove SMS related to removed user.
115         IntentFilter userIntentFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
116         getContext().registerReceiver(mUserIntentReceiver, userIntentFilter,
117                 Context.RECEIVER_NOT_EXPORTED);
118 
119         return true;
120     }
121 
hasCalling()122     private boolean hasCalling() {
123         return getContext().getPackageManager().hasSystemFeature(
124                 PackageManager.FEATURE_TELEPHONY_CALLING);
125     }
126 
127     /**
128      * Return the proper view of "sms" table for the current access status.
129      *
130      * @param accessRestricted If the access is restricted
131      * @return the table/view name of the "sms" data
132      */
getSmsTable(boolean accessRestricted)133     public static String getSmsTable(boolean accessRestricted) {
134         return accessRestricted ? VIEW_SMS_RESTRICTED : TABLE_SMS;
135     }
136 
137     @Override
query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort)138     public Cursor query(Uri url, String[] projectionIn, String selection,
139             String[] selectionArgs, String sort) {
140         String callingPackage = getCallingPackage();
141         final int callingUid = Binder.getCallingUid();
142         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
143 
144         // First check if a restricted view of the "sms" table should be used based on the
145         // caller's identity. Only system, phone or the default sms app can have full access
146         // of sms data. For other apps, we present a restricted view which only contains sent
147         // or received messages.
148         final boolean accessRestricted = ProviderUtil.isAccessRestricted(
149                 getContext(), getCallingPackage(), callingUid);
150         final String smsTable = getSmsTable(accessRestricted);
151         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
152 
153         // If access is restricted, we don't allow subqueries in the query.
154         if (accessRestricted) {
155             try {
156                 SqlQueryChecker.checkQueryParametersForSubqueries(projectionIn, selection, sort);
157             } catch (IllegalArgumentException e) {
158                 Log.w(TAG, "Query rejected: " + e.getMessage());
159                 return null;
160             }
161         }
162 
163         Cursor emptyCursor = new MatrixCursor((projectionIn == null) ?
164                 (new String[] {}) : projectionIn);
165 
166         // Generate the body of the query.
167         int match = sURLMatcher.match(url);
168         SQLiteDatabase db = getReadableDatabase(match);
169         SQLiteOpenHelper sqLiteOpenHelper = getDBOpenHelper(match);
170         if (sqLiteOpenHelper instanceof MmsSmsDatabaseHelper) {
171             ((MmsSmsDatabaseHelper) sqLiteOpenHelper).addDatabaseOpeningDebugLog(
172                     callingPackage + ";SmsProvider.query;" + url, true);
173         }
174         switch (match) {
175             case SMS_ALL:
176                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable);
177                 break;
178 
179             case SMS_UNDELIVERED:
180                 constructQueryForUndelivered(qb, smsTable);
181                 break;
182 
183             case SMS_FAILED:
184                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED, smsTable);
185                 break;
186 
187             case SMS_QUEUED:
188                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED, smsTable);
189                 break;
190 
191             case SMS_INBOX:
192                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX, smsTable);
193                 break;
194 
195             case SMS_SENT:
196                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT, smsTable);
197                 break;
198 
199             case SMS_DRAFT:
200                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT, smsTable);
201                 break;
202 
203             case SMS_OUTBOX:
204                 constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX, smsTable);
205                 break;
206 
207             case SMS_ALL_ID:
208                 qb.setTables(smsTable);
209                 qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")");
210                 break;
211 
212             case SMS_INBOX_ID:
213             case SMS_FAILED_ID:
214             case SMS_SENT_ID:
215             case SMS_DRAFT_ID:
216             case SMS_OUTBOX_ID:
217                 qb.setTables(smsTable);
218                 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
219                 break;
220 
221             case SMS_CONVERSATIONS_ID:
222                 int threadID;
223 
224                 try {
225                     threadID = Integer.parseInt(url.getPathSegments().get(1));
226                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
227                         Log.d(TAG, "query conversations: threadID=" + threadID);
228                     }
229                 }
230                 catch (Exception ex) {
231                     Log.e(TAG,
232                           "Bad conversation thread id: "
233                           + url.getPathSegments().get(1));
234                     return null;
235                 }
236 
237                 qb.setTables(smsTable);
238                 qb.appendWhere("thread_id = " + threadID);
239                 break;
240 
241             case SMS_CONVERSATIONS:
242                 qb.setTables(smsTable + ", "
243                         + "(SELECT thread_id AS group_thread_id, "
244                         + "MAX(date) AS group_date, "
245                         + "COUNT(*) AS msg_count "
246                         + "FROM " + smsTable + " "
247                         + "GROUP BY thread_id) AS groups");
248                 qb.appendWhere(smsTable + ".thread_id=groups.group_thread_id"
249                         + " AND " + smsTable + ".date=groups.group_date");
250                 final HashMap<String, String> projectionMap = new HashMap<>();
251                 projectionMap.put(Sms.Conversations.SNIPPET,
252                         smsTable + ".body AS snippet");
253                 projectionMap.put(Sms.Conversations.THREAD_ID,
254                         smsTable + ".thread_id AS thread_id");
255                 projectionMap.put(Sms.Conversations.MESSAGE_COUNT,
256                         "groups.msg_count AS msg_count");
257                 projectionMap.put("delta", null);
258                 qb.setProjectionMap(projectionMap);
259                 break;
260 
261             case SMS_RAW_MESSAGE:
262                 // before querying purge old entries with deleted = 1
263                 purgeDeletedMessagesInRawTable(db);
264                 qb.setTables("raw");
265                 break;
266 
267             case SMS_STATUS_PENDING:
268                 qb.setTables("sr_pending");
269                 break;
270 
271             case SMS_ATTACHMENT:
272                 qb.setTables("attachments");
273                 break;
274 
275             case SMS_ATTACHMENT_ID:
276                 qb.setTables("attachments");
277                 qb.appendWhere(
278                         "(sms_id = " + url.getPathSegments().get(1) + ")");
279                 break;
280 
281             case SMS_QUERY_THREAD_ID:
282                 qb.setTables("canonical_addresses");
283                 if (projectionIn == null) {
284                     projectionIn = sIDProjection;
285                 }
286                 break;
287 
288             case SMS_STATUS_ID:
289                 qb.setTables(smsTable);
290                 qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
291                 break;
292 
293             case SMS_ALL_ICC:
294             case SMS_ALL_ICC_SUBID: {
295                 int subId;
296                 if (match == SMS_ALL_ICC) {
297                     subId = SmsManager.getDefaultSmsSubscriptionId();
298                 } else {
299                     try {
300                         subId = Integer.parseInt(url.getPathSegments().get(1));
301                     } catch (NumberFormatException e) {
302                         throw new IllegalArgumentException("Wrong path segements, uri= " + url);
303                     }
304                 }
305 
306                 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(),
307                         subId, callerUserHandle)) {
308                     // If subId is not associated with user, return empty cursor.
309                     return emptyCursor;
310                 }
311 
312                 Cursor ret = getAllMessagesFromIcc(subId);
313                 ret.setNotificationUri(getContext().getContentResolver(),
314                         match == SMS_ALL_ICC ? ICC_URI : ICC_SUBID_URI);
315                 return ret;
316             }
317 
318             case SMS_ICC:
319             case SMS_ICC_SUBID: {
320                 int subId;
321                 int messageIndex;
322                 try {
323                     if (match == SMS_ICC) {
324                         subId = SmsManager.getDefaultSmsSubscriptionId();
325                         messageIndex = Integer.parseInt(url.getPathSegments().get(1));
326                     } else {
327                         subId = Integer.parseInt(url.getPathSegments().get(1));
328                         messageIndex = Integer.parseInt(url.getPathSegments().get(2));
329                     }
330                 } catch (NumberFormatException e) {
331                     throw new IllegalArgumentException("Wrong path segements, uri= " + url);
332                 }
333 
334                 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(),
335                         subId, callerUserHandle)) {
336                     // If subId is not associated with user, return empty cursor.
337                     return emptyCursor;
338                 }
339 
340                 Cursor ret = getSingleMessageFromIcc(subId, messageIndex);
341                 ret.setNotificationUri(getContext().getContentResolver(),
342                         match == SMS_ICC ? ICC_URI : ICC_SUBID_URI);
343                 return ret;
344             }
345 
346             default:
347                 Log.e(TAG, "Invalid request: " + url);
348                 return null;
349         }
350 
351         final long token = Binder.clearCallingIdentity();
352         String selectionBySubIds = null;
353         String selectionByEmergencyNumbers = null;
354         try {
355             // Filter SMS based on subId and emergency numbers.
356             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
357                     callerUserHandle);
358             if (hasCalling() && qb.getTables().equals(smsTable)) {
359                 selectionByEmergencyNumbers = ProviderUtil
360                         .getSelectionByEmergencyNumbers(getContext());
361             }
362         } finally {
363             Binder.restoreCallingIdentity(token);
364         }
365 
366         if (qb.getTables().equals(smsTable)) {
367             if (selectionBySubIds == null && selectionByEmergencyNumbers == null) {
368                 // No subscriptions associated with user
369                 // and no emergency numbers return empty cursor.
370                 return emptyCursor;
371             }
372         } else {
373             if (selectionBySubIds == null) {
374                 // No subscriptions associated with user return empty cursor.
375                 return emptyCursor;
376             }
377         }
378 
379         String filter = "";
380         if (selectionBySubIds != null && selectionByEmergencyNumbers != null) {
381             filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers);
382         } else {
383             filter = selectionBySubIds == null ?
384                     selectionByEmergencyNumbers : selectionBySubIds;
385         }
386         selection = DatabaseUtils.concatenateWhere(selection, filter);
387 
388         String orderBy = null;
389 
390         if (!TextUtils.isEmpty(sort)) {
391             orderBy = sort;
392         } else if (qb.getTables().equals(smsTable)) {
393             orderBy = Sms.DEFAULT_SORT_ORDER;
394         }
395 
396         Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
397                               null, null, orderBy);
398         // TODO: Since the URLs are a mess, always use content://sms
399         ret.setNotificationUri(getContext().getContentResolver(),
400                 NOTIFICATION_URI);
401         return ret;
402     }
403 
purgeDeletedMessagesInRawTable(SQLiteDatabase db)404     private void purgeDeletedMessagesInRawTable(SQLiteDatabase db) {
405         long oldTimestamp = System.currentTimeMillis() - RAW_MESSAGE_EXPIRE_AGE_MS;
406         int num = db.delete(TABLE_RAW, "deleted = 1 AND date < " + oldTimestamp, null);
407         if (Log.isLoggable(TAG, Log.VERBOSE)) {
408             Log.d(TAG, "purgeDeletedMessagesInRawTable: num rows older than " + oldTimestamp +
409                     " purged: " + num);
410         }
411     }
412 
getDBOpenHelper(int match)413     private SQLiteOpenHelper getDBOpenHelper(int match) {
414         // Raw table is stored on de database. Other tables are stored in ce database.
415         if (match == SMS_RAW_MESSAGE || match == SMS_RAW_MESSAGE_PERMANENT_DELETE) {
416             return mDeOpenHelper;
417         }
418         return mCeOpenHelper;
419     }
420 
convertIccToSms(SmsMessage message, int id)421     private Object[] convertIccToSms(SmsMessage message, int id) {
422         int statusOnIcc = message.getStatusOnIcc();
423         int type = Sms.MESSAGE_TYPE_ALL;
424         switch (statusOnIcc) {
425             case SmsManager.STATUS_ON_ICC_READ:
426             case SmsManager.STATUS_ON_ICC_UNREAD:
427                 type = Sms.MESSAGE_TYPE_INBOX;
428                 break;
429             case SmsManager.STATUS_ON_ICC_SENT:
430                 type = Sms.MESSAGE_TYPE_SENT;
431                 break;
432             case SmsManager.STATUS_ON_ICC_UNSENT:
433                 type = Sms.MESSAGE_TYPE_OUTBOX;
434                 break;
435         }
436 
437         String address = (type == Sms.MESSAGE_TYPE_INBOX)
438                 ? message.getDisplayOriginatingAddress()
439                 : message.getRecipientAddress();
440 
441         int index = message.getIndexOnIcc();
442         if (address == null) {
443             // The status byte of an EF_SMS record may not be correct. try to read other address
444             // type again.
445             Log.e(TAG, "convertIccToSms: EF_SMS(" + index + ")=> address=null, type=" + type
446                     + ", status=" + statusOnIcc + "(may not be correct). fallback to other type.");
447             address = (type == Sms.MESSAGE_TYPE_INBOX)
448                     ? message.getRecipientAddress()
449                     : message.getDisplayOriginatingAddress();
450 
451             if (address != null) {
452                 // Rely on actual PDU(address) to set type again.
453                 type = (type == Sms.MESSAGE_TYPE_INBOX)
454                         ? Sms.MESSAGE_TYPE_SENT
455                         : Sms.MESSAGE_TYPE_INBOX;
456                 Log.d(TAG, "convertIccToSms: new type=" + type + ", address=xxxxxx");
457             } else {
458                 Log.e(TAG, "convertIccToSms: no change");
459             }
460         }
461 
462         // N.B.: These calls must appear in the same order as the
463         // columns appear in ICC_COLUMNS.
464         Object[] row = new Object[13];
465         row[0] = message.getServiceCenterAddress();
466         row[1] = address;
467         row[2] = String.valueOf(message.getMessageClass());
468         row[3] = message.getDisplayMessageBody();
469         row[4] = message.getTimestampMillis();
470         row[5] = statusOnIcc;
471         row[6] = index;
472         row[7] = message.isStatusReportMessage();
473         row[8] = "sms";
474         row[9] = type;
475         row[10] = 0;      // locked
476         row[11] = NO_ERROR_CODE;
477         row[12] = id;
478         return row;
479     }
480 
481     /**
482      * Gets single message from the ICC for a subscription ID.
483      *
484      * @param subId the subscription ID.
485      * @param messageIndex the message index of the messaage in the ICC (1-based index).
486      * @return a cursor containing just one message from the ICC for the subscription ID.
487      */
getSingleMessageFromIcc(int subId, int messageIndex)488     private Cursor getSingleMessageFromIcc(int subId, int messageIndex) {
489         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
490             throw new IllegalArgumentException("Invalid Subscription ID " + subId);
491         }
492         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
493         List<SmsMessage> messages;
494 
495         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
496         long token = Binder.clearCallingIdentity();
497         try {
498             // getMessagesFromIcc() returns a zero-based list of valid messages in the ICC.
499             messages = smsManager.getMessagesFromIcc();
500         } finally {
501             Binder.restoreCallingIdentity(token);
502         }
503 
504         final int count = messages.size();
505         for (int i = 0; i < count; i++) {
506             SmsMessage message = messages.get(i);
507             if (message != null && message.getIndexOnIcc() == messageIndex) {
508                 MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1);
509                 cursor.addRow(convertIccToSms(message, 0));
510                 return cursor;
511             }
512         }
513 
514         throw new IllegalArgumentException(
515                 "No message in index " + messageIndex + " for subId " + subId);
516     }
517 
518     /**
519      * Gets all the messages in the ICC for a subscription ID.
520      *
521      * @param subId the subscription ID.
522      * @return a cursor listing all the message in the ICC for the subscription ID.
523      */
getAllMessagesFromIcc(int subId)524     private Cursor getAllMessagesFromIcc(int subId) {
525         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
526             throw new IllegalArgumentException("Invalid Subscription ID " + subId);
527         }
528         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
529         List<SmsMessage> messages;
530 
531         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call
532         long token = Binder.clearCallingIdentity();
533         try {
534             // getMessagesFromIcc() returns a zero-based list of valid messages in the ICC.
535             messages = smsManager.getMessagesFromIcc();
536         } finally {
537             Binder.restoreCallingIdentity(token);
538         }
539 
540         final int count = messages.size();
541         MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, count);
542         for (int i = 0; i < count; i++) {
543             SmsMessage message = messages.get(i);
544             if (message != null) {
545                 cursor.addRow(convertIccToSms(message, i));
546             }
547         }
548         return cursor;
549     }
550 
constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable)551     private void constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable) {
552         qb.setTables(smsTable);
553 
554         if (type != Sms.MESSAGE_TYPE_ALL) {
555             qb.appendWhere("type=" + type);
556         }
557     }
558 
constructQueryForUndelivered(SQLiteQueryBuilder qb, String smsTable)559     private void constructQueryForUndelivered(SQLiteQueryBuilder qb, String smsTable) {
560         qb.setTables(smsTable);
561 
562         qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX +
563                        " OR type=" + Sms.MESSAGE_TYPE_FAILED +
564                        " OR type=" + Sms.MESSAGE_TYPE_QUEUED + ")");
565     }
566 
567     @Override
getType(Uri url)568     public String getType(Uri url) {
569         switch (url.getPathSegments().size()) {
570         case 0:
571             return VND_ANDROID_DIR_SMS;
572             case 1:
573                 try {
574                     Integer.parseInt(url.getPathSegments().get(0));
575                     return VND_ANDROID_SMS;
576                 } catch (NumberFormatException ex) {
577                     return VND_ANDROID_DIR_SMS;
578                 }
579             case 2:
580                 // TODO: What about "threadID"?
581                 if (url.getPathSegments().get(0).equals("conversations")) {
582                     return VND_ANDROID_SMSCHAT;
583                 } else {
584                     return VND_ANDROID_SMS;
585                 }
586         }
587         return null;
588     }
589 
590     @Override
bulkInsert(@onNull Uri url, @NonNull ContentValues[] values)591     public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values) {
592         final int callerUid = Binder.getCallingUid();
593         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
594         final String callerPkg = getCallingPackage();
595         long token = Binder.clearCallingIdentity();
596         try {
597             int messagesInserted = 0;
598             for (ContentValues initialValues : values) {
599                 Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg,
600                         callerUserHandle);
601                 if (insertUri != null) {
602                     messagesInserted++;
603                 }
604             }
605 
606             // The raw table is used by the telephony layer for storing an sms before
607             // sending out a notification that an sms has arrived. We don't want to notify
608             // the default sms app of changes to this table.
609             final boolean notifyIfNotDefault = sURLMatcher.match(url) != SMS_RAW_MESSAGE;
610             notifyChange(notifyIfNotDefault, url, callerPkg);
611             return messagesInserted;
612         } finally {
613             Binder.restoreCallingIdentity(token);
614         }
615     }
616 
617     @Override
insert(Uri url, ContentValues initialValues)618     public Uri insert(Uri url, ContentValues initialValues) {
619         final int callerUid = Binder.getCallingUid();
620         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
621         final String callerPkg = getCallingPackage();
622         long token = Binder.clearCallingIdentity();
623         try {
624             Uri insertUri = insertInner(url, initialValues, callerUid, callerPkg, callerUserHandle);
625 
626             // Skip notifyChange() if insertUri is null
627             if (insertUri != null) {
628                 int match = sURLMatcher.match(url);
629                 // The raw table is used by the telephony layer for storing an sms before sending
630                 // out a notification that an sms has arrived. We don't want to notify the default
631                 // sms app of changes to this table.
632                 final boolean notifyIfNotDefault = match != SMS_RAW_MESSAGE;
633                 notifyChange(notifyIfNotDefault, insertUri, callerPkg);
634             }
635             return insertUri;
636         } finally {
637             Binder.restoreCallingIdentity(token);
638         }
639     }
640 
insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg, UserHandle callerUserHandle)641     private Uri insertInner(Uri url, ContentValues initialValues, int callerUid, String callerPkg,
642             UserHandle callerUserHandle) {
643         ContentValues values;
644         long rowID;
645         int type = Sms.MESSAGE_TYPE_ALL;
646 
647         int match = sURLMatcher.match(url);
648         String table = TABLE_SMS;
649 
650         switch (match) {
651             case SMS_ALL:
652                 Integer typeObj = initialValues.getAsInteger(Sms.TYPE);
653                 if (typeObj != null) {
654                     type = typeObj.intValue();
655                 } else {
656                     // default to inbox
657                     type = Sms.MESSAGE_TYPE_INBOX;
658                 }
659                 break;
660 
661             case SMS_INBOX:
662                 type = Sms.MESSAGE_TYPE_INBOX;
663                 break;
664 
665             case SMS_FAILED:
666                 type = Sms.MESSAGE_TYPE_FAILED;
667                 break;
668 
669             case SMS_QUEUED:
670                 type = Sms.MESSAGE_TYPE_QUEUED;
671                 break;
672 
673             case SMS_SENT:
674                 type = Sms.MESSAGE_TYPE_SENT;
675                 break;
676 
677             case SMS_DRAFT:
678                 type = Sms.MESSAGE_TYPE_DRAFT;
679                 break;
680 
681             case SMS_OUTBOX:
682                 type = Sms.MESSAGE_TYPE_OUTBOX;
683                 break;
684 
685             case SMS_RAW_MESSAGE:
686                 table = "raw";
687                 break;
688 
689             case SMS_STATUS_PENDING:
690                 table = "sr_pending";
691                 break;
692 
693             case SMS_ATTACHMENT:
694                 table = "attachments";
695                 break;
696 
697             case SMS_NEW_THREAD_ID:
698                 table = "canonical_addresses";
699                 break;
700 
701             case SMS_ALL_ICC:
702             case SMS_ALL_ICC_SUBID:
703                 int subId;
704                 if (match == SMS_ALL_ICC) {
705                     subId = SmsManager.getDefaultSmsSubscriptionId();
706                 } else {
707                     try {
708                         subId = Integer.parseInt(url.getPathSegments().get(1));
709                     } catch (NumberFormatException e) {
710                         throw new IllegalArgumentException(
711                                 "Wrong path segements for SMS_ALL_ICC_SUBID, uri= " + url);
712                     }
713                 }
714 
715                 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(), subId,
716                     callerUserHandle)) {
717                     TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(),
718                         subId, callerUid, callerPkg);
719                     return null;
720                 }
721 
722                 if (initialValues == null) {
723                     throw new IllegalArgumentException("ContentValues is null");
724                 }
725 
726                 String scAddress = initialValues.getAsString(Sms.SERVICE_CENTER);
727                 String address = initialValues.getAsString(Sms.ADDRESS);
728                 String message = initialValues.getAsString(Sms.BODY);
729                 boolean isRead = true;
730                 Integer obj = initialValues.getAsInteger(Sms.TYPE);
731 
732                 if (obj == null || address == null || message == null) {
733                     throw new IllegalArgumentException("Missing SMS data");
734                 }
735 
736                 type = obj.intValue();
737                 if (!isSupportedType(type)) {
738                     throw new IllegalArgumentException("Unsupported message type= " + type);
739                 }
740                 obj = initialValues.getAsInteger(Sms.READ); // 0: Unread, 1: Read
741                 if (obj != null && obj.intValue() == 0) {
742                     isRead = false;
743                 }
744 
745                 Long date = initialValues.getAsLong(Sms.DATE);
746                 return insertMessageToIcc(subId, scAddress, address, message, type, isRead,
747                         date != null ? date : 0) ? url : null;
748 
749             default:
750                 Log.e(TAG, "Invalid request: " + url);
751                 return null;
752         }
753 
754         SQLiteDatabase db = getWritableDatabase(match);
755         SQLiteOpenHelper sqLiteOpenHelper = getDBOpenHelper(match);
756         if (sqLiteOpenHelper instanceof MmsSmsDatabaseHelper) {
757             ((MmsSmsDatabaseHelper) sqLiteOpenHelper).addDatabaseOpeningDebugLog(
758                     callerPkg + ";SmsProvider.insert;" + url, false);
759         }
760 
761         if (table.equals(TABLE_SMS)) {
762             boolean addDate = false;
763             boolean addType = false;
764 
765             // Make sure that the date and type are set
766             if (initialValues == null) {
767                 values = new ContentValues(1);
768                 addDate = true;
769                 addType = true;
770             } else {
771                 values = new ContentValues(initialValues);
772 
773                 if (!initialValues.containsKey(Sms.DATE)) {
774                     addDate = true;
775                 }
776 
777                 if (!initialValues.containsKey(Sms.TYPE)) {
778                     addType = true;
779                 }
780             }
781 
782             if (addDate) {
783                 values.put(Sms.DATE, new Long(System.currentTimeMillis()));
784             }
785 
786             if (addType && (type != Sms.MESSAGE_TYPE_ALL)) {
787                 values.put(Sms.TYPE, Integer.valueOf(type));
788             }
789 
790             // thread_id
791             Long threadId = values.getAsLong(Sms.THREAD_ID);
792             String address = values.getAsString(Sms.ADDRESS);
793 
794             if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) {
795                 values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId(
796                                    getContext(), address));
797             }
798 
799             // If this message is going in as a draft, it should replace any
800             // other draft messages in the thread.  Just delete all draft
801             // messages with this thread ID.  We could add an OR REPLACE to
802             // the insert below, but we'd have to query to find the old _id
803             // to produce a conflict anyway.
804             if (values.getAsInteger(Sms.TYPE) == Sms.MESSAGE_TYPE_DRAFT) {
805                 db.delete(TABLE_SMS, "thread_id=? AND type=?",
806                         new String[] { values.getAsString(Sms.THREAD_ID),
807                                        Integer.toString(Sms.MESSAGE_TYPE_DRAFT) });
808             }
809 
810             if (type == Sms.MESSAGE_TYPE_INBOX) {
811                 // Look up the person if not already filled in.
812                 if ((values.getAsLong(Sms.PERSON) == null) && (!TextUtils.isEmpty(address))) {
813                     Cursor cursor = null;
814                     Uri uri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL,
815                             Uri.encode(address));
816                     try {
817                         cursor = getContext().getContentResolver().query(
818                                 uri,
819                                 CONTACT_QUERY_PROJECTION,
820                                 null, null, null);
821 
822                         if (cursor != null && cursor.moveToFirst()) {
823                             Long id = Long.valueOf(cursor.getLong(PERSON_ID_COLUMN));
824                             values.put(Sms.PERSON, id);
825                         }
826                     } catch (Exception ex) {
827                         Log.e(TAG, "insert: query contact uri " + uri + " caught ", ex);
828                     } finally {
829                         if (cursor != null) {
830                             cursor.close();
831                         }
832                     }
833                 }
834             } else {
835                 // Mark all non-inbox messages read.
836                 values.put(Sms.READ, ONE);
837             }
838             if (ProviderUtil.shouldSetCreator(values, callerUid)) {
839                 // Only SYSTEM or PHONE can set CREATOR
840                 // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR
841                 // set CREATOR using the truth on caller.
842                 // Note: Inferring package name from UID may include unrelated package names
843                 values.put(Sms.CREATOR, callerPkg);
844             }
845         } else {
846             if (initialValues == null) {
847                 values = new ContentValues(1);
848             } else {
849                 values = initialValues;
850             }
851         }
852 
853         // Insert subId value
854         int subId;
855         if (values.containsKey(Telephony.Sms.SUBSCRIPTION_ID)) {
856             subId = values.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID);
857         } else {
858             // TODO (b/256992531): Currently, one sim card is set as default sms subId in work
859             //  profile. Default sms subId should be updated based on user pref.
860             subId = SmsManager.getDefaultSmsSubscriptionId();
861             if (SubscriptionManager.isValidSubscriptionId(subId)) {
862                 values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
863             }
864         }
865 
866 
867         if (table.equals(TABLE_SMS)) {
868             // Get destination address from values
869             String address = "";
870             if (values.containsKey(Sms.ADDRESS)) {
871                 address = values.getAsString(Sms.ADDRESS);
872             }
873 
874             if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
875                     && !TelephonyPermissions.checkSubscriptionAssociatedWithUser(getContext(),
876                     subId, callerUserHandle, address)) {
877                 TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(getContext(),
878                         subId, callerUid, callerPkg);
879                 return null;
880             }
881         }
882 
883         rowID = db.insert(table, "body", values);
884 
885         // Don't use a trigger for updating the words table because of a bug
886         // in FTS3.  The bug is such that the call to get the last inserted
887         // row is incorrect.
888         if (table == TABLE_SMS) {
889             // Update the words table with a corresponding row.  The words table
890             // allows us to search for words quickly, without scanning the whole
891             // table;
892             ContentValues cv = new ContentValues();
893             cv.put(Telephony.MmsSms.WordsTable.ID, rowID);
894             cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("body"));
895             cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowID);
896             cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1);
897             cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, subId);
898             db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
899         }
900         if (rowID > 0) {
901             Uri uri = null;
902             if (table == TABLE_SMS) {
903                 uri = Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(rowID));
904             } else {
905                 uri = Uri.withAppendedPath(url, String.valueOf(rowID));
906             }
907             if (Log.isLoggable(TAG, Log.VERBOSE)) {
908                 Log.d(TAG, "insert " + uri + " succeeded");
909             }
910             return uri;
911         } else {
912             Log.e(TAG, "insert: failed!");
913             if (sqLiteOpenHelper instanceof MmsSmsDatabaseHelper) {
914                 ((MmsSmsDatabaseHelper) sqLiteOpenHelper).printDatabaseOpeningDebugLog();
915             }
916         }
917 
918         return null;
919     }
920 
isSupportedType(int messageType)921     private boolean isSupportedType(int messageType) {
922         return (messageType == Sms.MESSAGE_TYPE_INBOX)
923                 || (messageType == Sms.MESSAGE_TYPE_OUTBOX)
924                 || (messageType == Sms.MESSAGE_TYPE_SENT);
925     }
926 
getMessageStatusForIcc(int messageType, boolean isRead)927     private int getMessageStatusForIcc(int messageType, boolean isRead) {
928         if (messageType == Sms.MESSAGE_TYPE_SENT) {
929             return SmsManager.STATUS_ON_ICC_SENT;
930         } else if (messageType == Sms.MESSAGE_TYPE_OUTBOX) {
931             return SmsManager.STATUS_ON_ICC_UNSENT;
932         } else { // Sms.MESSAGE_BOX_INBOX
933             if (isRead) {
934                 return SmsManager.STATUS_ON_ICC_READ;
935             } else {
936                 return SmsManager.STATUS_ON_ICC_UNREAD;
937             }
938         }
939     }
940 
941     /**
942      * Inserts new message to the ICC for a subscription ID.
943      *
944      * @param subId the subscription ID.
945      * @param scAddress the SMSC for this message.
946      * @param address destination or originating address.
947      * @param message the message text.
948      * @param messageType type of the message.
949      * @param isRead ture if the message has been read. Otherwise false.
950      * @param date the date the message was received.
951      * @return true for succeess. Otherwise false.
952      */
insertMessageToIcc(int subId, String scAddress, String address, String message, int messageType, boolean isRead, long date)953     private boolean insertMessageToIcc(int subId, String scAddress, String address, String message,
954             int messageType, boolean isRead, long date) {
955         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
956             throw new IllegalArgumentException("Invalid Subscription ID " + subId);
957         }
958         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
959 
960         int status = getMessageStatusForIcc(messageType, isRead);
961         SmsMessage.SubmitPdu smsPdu =
962                 SmsMessage.getSmsPdu(subId, status, scAddress, address, message, date);
963 
964         if (smsPdu == null) {
965             throw new IllegalArgumentException("Failed to create SMS PDU");
966         }
967 
968         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
969         long token = Binder.clearCallingIdentity();
970         try {
971             return smsManager.copyMessageToIcc(
972                     smsPdu.encodedScAddress, smsPdu.encodedMessage, status);
973         } finally {
974             Binder.restoreCallingIdentity(token);
975         }
976     }
977 
978     @Override
delete(Uri url, String where, String[] whereArgs)979     public int delete(Uri url, String where, String[] whereArgs) {
980         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
981         final int callerUid = Binder.getCallingUid();
982         final long token = Binder.clearCallingIdentity();
983 
984         String selectionBySubIds = null;
985         String selectionByEmergencyNumbers = null;
986         try {
987             // Filter SMS based on subId and emergency numbers.
988             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
989                     callerUserHandle);
990             selectionByEmergencyNumbers = ProviderUtil
991                     .getSelectionByEmergencyNumbers(getContext());
992         } finally {
993             Binder.restoreCallingIdentity(token);
994         }
995 
996         String filter = "";
997         if (selectionBySubIds == null && selectionByEmergencyNumbers == null) {
998             // No subscriptions associated with user and no emergency numbers
999             filter = null;
1000         } else if (selectionBySubIds != null && selectionByEmergencyNumbers != null) {
1001             filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers);
1002         } else {
1003             filter = selectionBySubIds == null ?
1004                     selectionByEmergencyNumbers : selectionBySubIds;
1005         }
1006 
1007         int count;
1008         int match = sURLMatcher.match(url);
1009         SQLiteDatabase db = getWritableDatabase(match);
1010         String debugMessage = getCallingPackage() + ";SmsProvider.delete;" + url;
1011         // Always log delete for debug purpose, as delete is a critical but non-frequent operation.
1012         Log.d(TAG, debugMessage);
1013         SQLiteOpenHelper sqLiteOpenHelper = getDBOpenHelper(match);
1014         if (sqLiteOpenHelper instanceof MmsSmsDatabaseHelper) {
1015             ((MmsSmsDatabaseHelper) sqLiteOpenHelper).addDatabaseOpeningDebugLog(
1016                     debugMessage, false);
1017         }
1018         boolean notifyIfNotDefault = true;
1019         switch (match) {
1020             case SMS_ALL:
1021                 if (filter == null) {
1022                     // No subscriptions associated with user and no emergency numbers, return 0.
1023                     return 0;
1024                 }
1025                 where = DatabaseUtils.concatenateWhere(where, filter);
1026                 count = db.delete(TABLE_SMS, where, whereArgs);
1027                 if (count != 0) {
1028                     // Don't update threads unless something changed.
1029                     MmsSmsDatabaseHelper.updateThreads(db, where, whereArgs);
1030                 }
1031                 break;
1032 
1033             case SMS_ALL_ID:
1034                 try {
1035                     int message_id = Integer.parseInt(url.getPathSegments().get(0));
1036                     count = MmsSmsDatabaseHelper.deleteOneSms(db, message_id);
1037                 } catch (Exception e) {
1038                     throw new IllegalArgumentException(
1039                         "Bad message id: " + url.getPathSegments().get(0));
1040                 }
1041                 break;
1042 
1043             case SMS_CONVERSATIONS_ID:
1044                 int threadID;
1045 
1046                 try {
1047                     threadID = Integer.parseInt(url.getPathSegments().get(1));
1048                 } catch (Exception ex) {
1049                     throw new IllegalArgumentException(
1050                             "Bad conversation thread id: "
1051                             + url.getPathSegments().get(1));
1052                 }
1053 
1054                 // delete the messages from the sms table
1055                 where = DatabaseUtils.concatenateWhere("thread_id=" + threadID, where);
1056                 if (filter == null) {
1057                     // No subscriptions associated with user and no emergency numbers, return 0.
1058                     return 0;
1059                 }
1060                 where = DatabaseUtils.concatenateWhere(where, filter);
1061                 count = db.delete(TABLE_SMS, where, whereArgs);
1062                 MmsSmsDatabaseHelper.updateThread(db, threadID);
1063                 break;
1064 
1065             case SMS_RAW_MESSAGE:
1066                 ContentValues cv = new ContentValues();
1067                 cv.put("deleted", 1);
1068                 count = db.update(TABLE_RAW, cv, where, whereArgs);
1069                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1070                     Log.d(TAG, "delete: num rows marked deleted in raw table: " + count);
1071                 }
1072                 notifyIfNotDefault = false;
1073                 break;
1074 
1075             case SMS_RAW_MESSAGE_PERMANENT_DELETE:
1076                 count = db.delete(TABLE_RAW, where, whereArgs);
1077                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1078                     Log.d(TAG, "delete: num rows permanently deleted in raw table: " + count);
1079                 }
1080                 notifyIfNotDefault = false;
1081                 break;
1082 
1083             case SMS_STATUS_PENDING:
1084                 if (selectionBySubIds == null) {
1085                     // No subscriptions associated with user, return 0.
1086                     return 0;
1087                 }
1088                 where = DatabaseUtils.concatenateWhere(where, selectionBySubIds);
1089                 count = db.delete("sr_pending", where, whereArgs);
1090                 break;
1091 
1092             case SMS_ALL_ICC:
1093             case SMS_ALL_ICC_SUBID: {
1094                 int subId;
1095                 int deletedCnt;
1096                 if (match == SMS_ALL_ICC) {
1097                     subId = SmsManager.getDefaultSmsSubscriptionId();
1098                 } else {
1099                     try {
1100                         subId = Integer.parseInt(url.getPathSegments().get(1));
1101                     } catch (NumberFormatException e) {
1102                         throw new IllegalArgumentException("Wrong path segements, uri= " + url);
1103                     }
1104                 }
1105 
1106                 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(),
1107                         subId, callerUserHandle)) {
1108                     // If subId is not associated with user, return 0.
1109                     return 0;
1110                 }
1111 
1112                 deletedCnt = deleteAllMessagesFromIcc(subId);
1113                 // Notify changes even failure case since there might be some changes should be
1114                 // known.
1115                 getContext()
1116                         .getContentResolver()
1117                         .notifyChange(
1118                                 match == SMS_ALL_ICC ? ICC_URI : ICC_SUBID_URI,
1119                                 null,
1120                                 true,
1121                                 UserHandle.USER_ALL);
1122                 return deletedCnt;
1123             }
1124 
1125             case SMS_ICC:
1126             case SMS_ICC_SUBID: {
1127                 int subId;
1128                 int messageIndex;
1129                 boolean success;
1130                 try {
1131                     if (match == SMS_ICC) {
1132                         subId = SmsManager.getDefaultSmsSubscriptionId();
1133                         messageIndex = Integer.parseInt(url.getPathSegments().get(1));
1134                     } else {
1135                         subId = Integer.parseInt(url.getPathSegments().get(1));
1136                         messageIndex = Integer.parseInt(url.getPathSegments().get(2));
1137                     }
1138                 } catch (NumberFormatException e) {
1139                     throw new IllegalArgumentException("Wrong path segements, uri= " + url);
1140                 }
1141 
1142                 if (!ProviderUtil.allowInteractingWithEntryOfSubscription(getContext(),
1143                         subId, callerUserHandle)) {
1144                     // If subId is not associated with user, return 0.
1145                     return 0;
1146                 }
1147 
1148                 success = deleteMessageFromIcc(subId, messageIndex);
1149                 // Notify changes even failure case since there might be some changes should be
1150                 // known.
1151                 getContext()
1152                         .getContentResolver()
1153                         .notifyChange(
1154                                 match == SMS_ICC ? ICC_URI : ICC_SUBID_URI,
1155                                 null,
1156                                 true,
1157                                 UserHandle.USER_ALL);
1158                 return success ? 1 : 0; // return deleted count
1159             }
1160 
1161             default:
1162                 throw new IllegalArgumentException("Unknown URL");
1163         }
1164 
1165         if (count > 0) {
1166             notifyChange(notifyIfNotDefault, url, getCallingPackage());
1167         }
1168         return count;
1169     }
1170 
1171     /**
1172      * Deletes the message at index from the ICC for a subscription ID.
1173      *
1174      * @param subId the subscription ID.
1175      * @param messageIndex the message index of the message in the ICC (1-based index).
1176      * @return true for succeess. Otherwise false.
1177      */
deleteMessageFromIcc(int subId, int messageIndex)1178     private boolean deleteMessageFromIcc(int subId, int messageIndex) {
1179         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1180             throw new IllegalArgumentException("Invalid Subscription ID " + subId);
1181         }
1182         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
1183 
1184         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
1185         long token = Binder.clearCallingIdentity();
1186         try {
1187             return smsManager.deleteMessageFromIcc(messageIndex);
1188         } finally {
1189             Binder.restoreCallingIdentity(token);
1190         }
1191     }
1192 
1193     /**
1194      * Deletes all the messages from the ICC for a subscription ID.
1195      *
1196      * @param subId the subscription ID.
1197      * @return return deleted messaegs count.
1198      */
deleteAllMessagesFromIcc(int subId)1199     private int deleteAllMessagesFromIcc(int subId) {
1200         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1201             throw new IllegalArgumentException("Invalid Subscription ID " + subId);
1202         }
1203         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
1204 
1205         // Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
1206         long token = Binder.clearCallingIdentity();
1207         try {
1208             int deletedCnt = 0;
1209             int maxIndex = smsManager.getSmsCapacityOnIcc();
1210             // messageIndex is 1-based index of the message in the ICC.
1211             for (int messageIndex = 1; messageIndex <= maxIndex; messageIndex++) {
1212                 if (smsManager.deleteMessageFromIcc(messageIndex)) {
1213                     deletedCnt++;
1214                 } else {
1215                     Log.e(TAG, "Fail to delete SMS at index " + messageIndex
1216                             + " for subId " + subId);
1217                 }
1218             }
1219             return deletedCnt;
1220         } finally {
1221             Binder.restoreCallingIdentity(token);
1222         }
1223     }
1224 
1225     @Override
update(Uri url, ContentValues values, String where, String[] whereArgs)1226     public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
1227         final int callerUid = Binder.getCallingUid();
1228         final UserHandle callerUserHandle = Binder.getCallingUserHandle();
1229         final String callerPkg = getCallingPackage();
1230         int count = 0;
1231         String table = TABLE_SMS;
1232         String extraWhere = null;
1233         boolean notifyIfNotDefault = true;
1234         int match = sURLMatcher.match(url);
1235         SQLiteDatabase db = getWritableDatabase(match);
1236         SQLiteOpenHelper sqLiteOpenHelper = getDBOpenHelper(match);
1237         if (sqLiteOpenHelper instanceof MmsSmsDatabaseHelper) {
1238             ((MmsSmsDatabaseHelper) sqLiteOpenHelper).addDatabaseOpeningDebugLog(
1239                     callerPkg + ";SmsProvider.update;" + url, false);
1240         }
1241 
1242         switch (match) {
1243             case SMS_RAW_MESSAGE:
1244                 table = TABLE_RAW;
1245                 notifyIfNotDefault = false;
1246                 break;
1247 
1248             case SMS_STATUS_PENDING:
1249                 table = TABLE_SR_PENDING;
1250                 break;
1251 
1252             case SMS_ALL:
1253             case SMS_FAILED:
1254             case SMS_QUEUED:
1255             case SMS_INBOX:
1256             case SMS_SENT:
1257             case SMS_DRAFT:
1258             case SMS_OUTBOX:
1259             case SMS_CONVERSATIONS:
1260                 break;
1261 
1262             case SMS_ALL_ID:
1263                 extraWhere = "_id=" + url.getPathSegments().get(0);
1264                 break;
1265 
1266             case SMS_INBOX_ID:
1267             case SMS_FAILED_ID:
1268             case SMS_SENT_ID:
1269             case SMS_DRAFT_ID:
1270             case SMS_OUTBOX_ID:
1271                 extraWhere = "_id=" + url.getPathSegments().get(1);
1272                 break;
1273 
1274             case SMS_CONVERSATIONS_ID: {
1275                 String threadId = url.getPathSegments().get(1);
1276 
1277                 try {
1278                     Integer.parseInt(threadId);
1279                 } catch (Exception ex) {
1280                     Log.e(TAG, "Bad conversation thread id: " + threadId);
1281                     break;
1282                 }
1283 
1284                 extraWhere = "thread_id=" + threadId;
1285                 break;
1286             }
1287 
1288             case SMS_STATUS_ID:
1289                 extraWhere = "_id=" + url.getPathSegments().get(1);
1290                 break;
1291 
1292             default:
1293                 throw new UnsupportedOperationException(
1294                         "URI " + url + " not supported");
1295         }
1296 
1297         if (table.equals(TABLE_SMS) && ProviderUtil.shouldRemoveCreator(values, callerUid)) {
1298             // CREATOR should not be changed by non-SYSTEM/PHONE apps
1299             Log.w(TAG, callerPkg + " tries to update CREATOR");
1300             values.remove(Sms.CREATOR);
1301         }
1302 
1303         final long token = Binder.clearCallingIdentity();
1304         String selectionBySubIds = null;
1305         String selectionByEmergencyNumbers = null;
1306         try {
1307             // Filter SMS based on subId and emergency numbers.
1308             selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
1309                     callerUserHandle);
1310             if (table.equals(TABLE_SMS)) {
1311                 selectionByEmergencyNumbers = ProviderUtil
1312                         .getSelectionByEmergencyNumbers(getContext());
1313             }
1314         } finally {
1315             Binder.restoreCallingIdentity(token);
1316         }
1317 
1318         if (table.equals(TABLE_SMS)) {
1319             if (selectionBySubIds == null && selectionByEmergencyNumbers == null) {
1320                 // No subscriptions associated with user and no emergency numbers, return 0.
1321                 return 0;
1322             }
1323         } else {
1324             if (selectionBySubIds == null) {
1325                 // No subscriptions associated with user, return 0.
1326                 return 0;
1327             }
1328         }
1329 
1330 
1331         String filter = "";
1332         if (selectionBySubIds != null && selectionByEmergencyNumbers != null) {
1333             filter = (selectionBySubIds + " OR " + selectionByEmergencyNumbers);
1334         } else {
1335             filter = selectionBySubIds == null ?
1336                     selectionByEmergencyNumbers : selectionBySubIds;
1337         }
1338         where = DatabaseUtils.concatenateWhere(where, filter);
1339 
1340         where = DatabaseUtils.concatenateWhere(where, extraWhere);
1341         count = db.update(table, values, where, whereArgs);
1342 
1343         if (count > 0) {
1344             if (Log.isLoggable(TAG, Log.VERBOSE)) {
1345                 Log.d(TAG, "update " + url + " succeeded");
1346             }
1347             notifyChange(notifyIfNotDefault, url, callerPkg);
1348         }
1349         return count;
1350     }
1351 
notifyChange(boolean notifyIfNotDefault, Uri uri, final String callingPackage)1352     private void notifyChange(boolean notifyIfNotDefault, Uri uri, final String callingPackage) {
1353         final Context context = getContext();
1354         ContentResolver cr = context.getContentResolver();
1355         cr.notifyChange(uri, null, true, UserHandle.USER_ALL);
1356         cr.notifyChange(MmsSms.CONTENT_URI, null, true, UserHandle.USER_ALL);
1357         cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null, true,
1358                 UserHandle.USER_ALL);
1359         if (notifyIfNotDefault) {
1360             ProviderUtil.notifyIfNotDefaultSmsApp(uri, callingPackage, context);
1361         }
1362     }
1363 
1364     // Db open helper for tables stored in CE(Credential Encrypted) storage.
1365     @VisibleForTesting
1366     public SQLiteOpenHelper mCeOpenHelper;
1367     // Db open helper for tables stored in DE(Device Encrypted) storage. It's currently only used
1368     // to store raw table.
1369     @VisibleForTesting
1370     public SQLiteOpenHelper mDeOpenHelper;
1371 
1372     private final static String TAG = "SmsProvider";
1373     private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms";
1374     private final static String VND_ANDROID_SMSCHAT =
1375             "vnd.android.cursor.item/sms-chat";
1376     private final static String VND_ANDROID_DIR_SMS =
1377             "vnd.android.cursor.dir/sms";
1378 
1379     private static final String[] sIDProjection = new String[] { "_id" };
1380 
1381     private static final int SMS_ALL = 0;
1382     private static final int SMS_ALL_ID = 1;
1383     private static final int SMS_INBOX = 2;
1384     private static final int SMS_INBOX_ID = 3;
1385     private static final int SMS_SENT = 4;
1386     private static final int SMS_SENT_ID = 5;
1387     private static final int SMS_DRAFT = 6;
1388     private static final int SMS_DRAFT_ID = 7;
1389     private static final int SMS_OUTBOX = 8;
1390     private static final int SMS_OUTBOX_ID = 9;
1391     private static final int SMS_CONVERSATIONS = 10;
1392     private static final int SMS_CONVERSATIONS_ID = 11;
1393     private static final int SMS_RAW_MESSAGE = 15;
1394     private static final int SMS_ATTACHMENT = 16;
1395     private static final int SMS_ATTACHMENT_ID = 17;
1396     private static final int SMS_NEW_THREAD_ID = 18;
1397     private static final int SMS_QUERY_THREAD_ID = 19;
1398     private static final int SMS_STATUS_ID = 20;
1399     private static final int SMS_STATUS_PENDING = 21;
1400     private static final int SMS_ALL_ICC = 22;
1401     private static final int SMS_ICC = 23;
1402     private static final int SMS_FAILED = 24;
1403     private static final int SMS_FAILED_ID = 25;
1404     private static final int SMS_QUEUED = 26;
1405     private static final int SMS_UNDELIVERED = 27;
1406     private static final int SMS_RAW_MESSAGE_PERMANENT_DELETE = 28;
1407     private static final int SMS_ALL_ICC_SUBID = 29;
1408     private static final int SMS_ICC_SUBID = 30;
1409 
1410     private static final UriMatcher sURLMatcher =
1411             new UriMatcher(UriMatcher.NO_MATCH);
1412 
1413     static {
1414         sURLMatcher.addURI("sms", null, SMS_ALL);
1415         sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
1416         sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
1417         sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
1418         sURLMatcher.addURI("sms", "sent", SMS_SENT);
1419         sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
1420         sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
1421         sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
1422         sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX);
1423         sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID);
1424         sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED);
1425         sURLMatcher.addURI("sms", "failed", SMS_FAILED);
1426         sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID);
1427         sURLMatcher.addURI("sms", "queued", SMS_QUEUED);
1428         sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);
1429         sURLMatcher.addURI("sms", "conversations/#", SMS_CONVERSATIONS_ID);
1430         sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE);
1431         sURLMatcher.addURI("sms", "raw/permanentDelete", SMS_RAW_MESSAGE_PERMANENT_DELETE);
1432         sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT);
1433         sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID);
1434         sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID);
1435         sURLMatcher.addURI("sms", "threadID/#", SMS_QUERY_THREAD_ID);
1436         sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID);
1437         sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
1438         sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC);
1439         sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
1440         sURLMatcher.addURI("sms", "icc_subId/#", SMS_ALL_ICC_SUBID);
1441         sURLMatcher.addURI("sms", "icc_subId/#/#", SMS_ICC_SUBID);
1442         //we keep these for not breaking old applications
1443         sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
1444         sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
1445     }
1446 
1447     /**
1448      * These methods can be overridden in a subclass for testing SmsProvider using an
1449      * in-memory database.
1450      */
getReadableDatabase(int match)1451     SQLiteDatabase getReadableDatabase(int match) {
1452         return getDBOpenHelper(match).getReadableDatabase();
1453     }
1454 
getWritableDatabase(int match)1455     SQLiteDatabase getWritableDatabase(int match) {
1456         return  getDBOpenHelper(match).getWritableDatabase();
1457     }
1458 
1459     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
1460         @Override
1461         public void onReceive(Context context, Intent intent) {
1462             switch (intent.getAction()) {
1463                 case Intent.ACTION_USER_REMOVED:
1464                     UserHandle userToBeRemoved  = intent.getParcelableExtra(Intent.EXTRA_USER,
1465                             UserHandle.class);
1466                     UserManager userManager = context.getSystemService(UserManager.class);
1467                     if ((userToBeRemoved == null) || (userManager == null) ||
1468                             (!userManager.isManagedProfile(userToBeRemoved.getIdentifier()))) {
1469                         // Do not delete SMS if removed profile is not managed profile.
1470                         return;
1471                     }
1472                     Log.d(TAG, "Received ACTION_USER_REMOVED for managed profile - Deleting SMS.");
1473 
1474                     // Deleting SMS related to managed profile.
1475                     Uri uri = Sms.CONTENT_URI;
1476                     int match = sURLMatcher.match(uri);
1477                     SQLiteDatabase db = getWritableDatabase(match);
1478 
1479                     final long token = Binder.clearCallingIdentity();
1480                     String selectionBySubIds;
1481                     try {
1482                         // Filter SMS based on subId.
1483                         selectionBySubIds = ProviderUtil.getSelectionBySubIds(getContext(),
1484                                 userToBeRemoved);
1485                     } finally {
1486                         Binder.restoreCallingIdentity(token);
1487                     }
1488                     if (selectionBySubIds == null) {
1489                         // No subscriptions associated with user, return.
1490                         return;
1491                     }
1492 
1493                     int count = db.delete(TABLE_SMS, selectionBySubIds, null);
1494                     if (count != 0) {
1495                         // Don't update threads unless something changed.
1496                         MmsSmsDatabaseHelper.updateThreads(db, selectionBySubIds, null);
1497                         notifyChange(true, uri, getCallingPackage());
1498                     }
1499                     break;
1500             }
1501         }
1502     };
1503 }
1504