1 /*
2  * Copyright (C) 2014 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.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.content.ComponentName;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.Process;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.provider.Telephony;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.telephony.emergency.EmergencyNumber;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import com.android.internal.telephony.SmsApplication;
39 import com.android.internal.telephony.TelephonyPermissions;
40 import com.android.internal.telephony.flags.Flags;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.stream.Collectors;
47 
48 /**
49  * Helpers
50  */
51 public class ProviderUtil {
52     private final static String TAG = "SmsProvider";
53     private static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
54     private static final int PHONE_UID = 1001;
55 
56     /**
57      * Check if a caller of the provider has restricted access,
58      * i.e. being non-system, non-phone, non-default SMS app
59      *
60      * @param context the context to use
61      * @param packageName the caller package name
62      * @param uid the caller uid
63      * @return true if the caller is not system, or phone or default sms app, false otherwise
64      */
isAccessRestricted(Context context, String packageName, int uid)65     public static boolean isAccessRestricted(Context context, String packageName, int uid) {
66         return (uid != Process.SYSTEM_UID
67                 && uid != Process.PHONE_UID
68                 && !SmsApplication.isDefaultSmsApplication(context, packageName));
69     }
70 
71     /**
72      * Whether should set CREATOR for an insertion
73      *
74      * @param values The content of the message
75      * @param uid The caller UID of the insertion
76      * @return true if we should set CREATOR, false otherwise
77      */
shouldSetCreator(ContentValues values, int uid)78     public static boolean shouldSetCreator(ContentValues values, int uid) {
79         return (uid != Process.SYSTEM_UID && uid != Process.PHONE_UID) ||
80                 (!values.containsKey(Telephony.Sms.CREATOR) &&
81                         !values.containsKey(Telephony.Mms.CREATOR));
82     }
83 
84     /**
85      * Whether should remove CREATOR for an update
86      *
87      * @param values The content of the message
88      * @param uid The caller UID of the update
89      * @return true if we should remove CREATOR, false otherwise
90      */
shouldRemoveCreator(ContentValues values, int uid)91     public static boolean shouldRemoveCreator(ContentValues values, int uid) {
92         return (uid != Process.SYSTEM_UID && uid != Process.PHONE_UID) &&
93                 (values.containsKey(Telephony.Sms.CREATOR) ||
94                         values.containsKey(Telephony.Mms.CREATOR));
95     }
96 
97     /**
98      * Notify the default SMS app of an SMS/MMS provider change if the change is being made
99      * by a package other than the default SMS app itself.
100      *
101      * @param uri The uri the provider change applies to
102      * @param callingPackage The package name of the provider caller
103      * @param Context
104      */
notifyIfNotDefaultSmsApp(final Uri uri, final String callingPackage, final Context context)105     public static void notifyIfNotDefaultSmsApp(final Uri uri, final String callingPackage,
106             final Context context) {
107         if (TextUtils.equals(callingPackage, Telephony.Sms.getDefaultSmsPackage(context))) {
108             if (Log.isLoggable(TAG, Log.VERBOSE)) {
109                 Log.d(TAG, "notifyIfNotDefaultSmsApp - called from default sms app");
110             }
111             return;
112         }
113         // Direct the intent to only the default SMS app, and only if the SMS app has a receiver
114         // for the intent.
115         ComponentName componentName =
116                 SmsApplication.getDefaultExternalTelephonyProviderChangedApplication(context, true);
117         if (componentName == null) {
118             return;     // the default sms app doesn't have a receiver for this intent
119         }
120 
121         final Intent intent =
122                 new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
123         intent.setFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
124         intent.setComponent(componentName);
125         if (uri != null) {
126             intent.setData(uri);
127         }
128         if (Log.isLoggable(TAG, Log.VERBOSE)) {
129             Log.d(TAG, "notifyIfNotDefaultSmsApp - called from " + callingPackage + ", notifying");
130         }
131         intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
132         context.sendBroadcast(intent);
133     }
134 
getCredentialEncryptedContext(Context context)135     public static Context getCredentialEncryptedContext(Context context) {
136         if (context.isCredentialProtectedStorage()) {
137             return context;
138         }
139         return context.createCredentialProtectedStorageContext();
140     }
141 
getDeviceEncryptedContext(Context context)142     public static Context getDeviceEncryptedContext(Context context) {
143         if (context.isDeviceProtectedStorage()) {
144             return context;
145         }
146         return context.createDeviceProtectedStorageContext();
147     }
148 
149     /**
150      * Get subscriptions associated with the user in the format of a selection string.
151      * @param context context
152      * @param userHandle caller user handle.
153      * @return subscriptions associated with the user in the format of a selection string
154      * or {@code null} if user is not associated with any subscription.
155      */
156     @Nullable
getSelectionBySubIds(Context context, @NonNull final UserHandle userHandle)157     public static String getSelectionBySubIds(Context context,
158             @NonNull final UserHandle userHandle) {
159         List<SubscriptionInfo> associatedSubscriptionsList = new ArrayList<>();
160         SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
161         UserManager userManager = context.getSystemService(UserManager.class);
162 
163         if (Flags.workProfileApiSplit()) {
164             if (subManager != null) {
165                 // Get list of subscriptions accessible to this user.
166                 associatedSubscriptionsList = subManager
167                         .getSubscriptionInfoListAssociatedWithUser(userHandle);
168 
169                 if ((userManager != null)
170                         && userManager.isManagedProfile(userHandle.getIdentifier())) {
171                     // Work profile caller can only see subscriptions explicitly associated with it.
172                     associatedSubscriptionsList = associatedSubscriptionsList.stream()
173                             .filter(info -> userHandle.equals(subManager
174                                             .getSubscriptionUserHandle(info.getSubscriptionId())))
175                             .collect(Collectors.toList());
176                 } else {
177                     // SMS/MMS restored from another device have sub_id=-1.
178                     // To query/update/delete those messages, sub_id=-1 should be in the selection
179                     // string.
180                     SubscriptionInfo invalidSubInfo = new SubscriptionInfo.Builder()
181                             .setId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
182                             .build();
183                     associatedSubscriptionsList.add(invalidSubInfo);
184                 }
185             }
186         } else {
187             if (subManager != null) {
188                 // Get list of subscriptions associated with this user.
189                 associatedSubscriptionsList = subManager
190                         .getSubscriptionInfoListAssociatedWithUser(userHandle);
191             }
192 
193             if ((userManager != null)
194                     && (!userManager.isManagedProfile(userHandle.getIdentifier()))) {
195                 // SMS/MMS restored from another device have sub_id=-1.
196                 // To query/update/delete those messages, sub_id=-1 should be in the selection
197                 // string.
198                 SubscriptionInfo invalidSubInfo = new SubscriptionInfo.Builder()
199                         .setId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
200                         .build();
201                 associatedSubscriptionsList.add(invalidSubInfo);
202             }
203         }
204 
205         if (associatedSubscriptionsList.isEmpty()) {
206             return null;
207         }
208 
209         // Converts [1,2,3,4,-1] to "'1','2','3','4','-1'" so that it can be appended to
210         // selection string
211         String subIdListStr = associatedSubscriptionsList.stream()
212                 .map(subInfo -> ("'" + subInfo.getSubscriptionId() + "'"))
213                 .collect(Collectors.joining(","));
214         String selectionBySubId = (Telephony.Sms.SUBSCRIPTION_ID + " IN (" + subIdListStr + ")");
215         if (Log.isLoggable(TAG, Log.VERBOSE)) {
216             Log.d(TAG, "getSelectionBySubIds: " + selectionBySubId);
217         }
218         return selectionBySubId;
219     }
220 
221     /**
222      * Get emergency number list in the format of a selection string.
223      * @param context context
224      * @return emergency number list in the format of a selection string
225      * or {@code null} if emergency number list is empty.
226      */
227     @Nullable
getSelectionByEmergencyNumbers(@onNull Context context)228     public static String getSelectionByEmergencyNumbers(@NonNull Context context) {
229         // Get emergency number list to add it to selection string.
230         TelephonyManager tm = context.getSystemService(TelephonyManager.class);
231         Map<Integer, List<EmergencyNumber>> emergencyNumberList = null;
232         try {
233             if (tm != null) {
234                 emergencyNumberList = tm.getEmergencyNumberList();
235             }
236         } catch (Exception e) {
237             Log.e(TAG, "Cannot get emergency number list", e);
238         }
239 
240         String selectionByEmergencyNumber = null;
241         if (emergencyNumberList != null && !emergencyNumberList.isEmpty()) {
242             String emergencyNumberListStr = "";
243             for (Map.Entry<Integer, List<EmergencyNumber>> entry : emergencyNumberList.entrySet()) {
244                 if (!emergencyNumberListStr.isEmpty() && !entry.getValue().isEmpty()) {
245                     emergencyNumberListStr += ',';
246                 }
247 
248                 emergencyNumberListStr += entry.getValue().stream()
249                         .map(emergencyNumber -> ("'" + emergencyNumber.getNumber() + "'"))
250                         .collect(Collectors.joining(","));
251             }
252             selectionByEmergencyNumber = Telephony.Sms.ADDRESS +
253                     " IN (" + emergencyNumberListStr + ")";
254         }
255         return selectionByEmergencyNumber;
256     }
257 
258     /**
259      * Check sub is either default value(for backup restore) or is accessible by the caller profile.
260      * @param ctx Context
261      * @param subId The sub Id associated with the entry
262      * @param callerUserHandle The user handle of the caller profile
263      * @return {@code true} if allow the caller to insert an entry that's associated with this sub.
264      */
allowInteractingWithEntryOfSubscription(Context ctx, int subId, UserHandle callerUserHandle)265     public static boolean allowInteractingWithEntryOfSubscription(Context ctx,
266             int subId, UserHandle callerUserHandle) {
267         return TelephonyPermissions
268                 .checkSubscriptionAssociatedWithUser(ctx, subId, callerUserHandle)
269                 // INVALID_SUBSCRIPTION_ID represents backup restore.
270                 || subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID;
271     }
272 
273     /**
274      * Log all running processes of the telephony provider package.
275      */
logRunningTelephonyProviderProcesses(@onNull Context context)276     public static void logRunningTelephonyProviderProcesses(@NonNull Context context) {
277         if (!Flags.logMmsSmsDatabaseAccessInfo()) {
278             return;
279         }
280 
281         ActivityManager am = context.getSystemService(ActivityManager.class);
282         if (am == null) {
283             Log.d(TAG, "logRunningTelephonyProviderProcesses: ActivityManager service is not"
284                     + " available");
285             return;
286         }
287 
288         List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
289         if (processInfos == null) {
290             Log.d(TAG, "logRunningTelephonyProviderProcesses: processInfos is null");
291             return;
292         }
293 
294         StringBuilder sb = new StringBuilder();
295         for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
296             if (Arrays.asList(processInfo.pkgList).contains(TELEPHONY_PROVIDER_PACKAGE)
297                     || processInfo.uid == PHONE_UID) {
298                 sb.append("{ProcessName=");
299                 sb.append(processInfo.processName);
300                 sb.append(";PID=");
301                 sb.append(processInfo.pid);
302                 sb.append(";UID=");
303                 sb.append(processInfo.uid);
304                 sb.append(";pkgList=");
305                 for (String pkg : processInfo.pkgList) {
306                     sb.append(pkg + ";");
307                 }
308                 sb.append("}");
309             }
310         }
311         Log.d(TAG, "RunningTelephonyProviderProcesses:" + sb.toString());
312     }
313 }
314