1 /*
2  * Copyright (C) 2019 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 package com.android.internal.telephony.util;
17 
18 import static android.telephony.Annotation.DataState;
19 import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
20 import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.ActivityManager;
25 import android.app.role.RoleManager;
26 import android.content.Context;
27 import android.content.pm.ComponentInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.os.Binder;
31 import android.os.Bundle;
32 import android.os.PersistableBundle;
33 import android.os.RemoteException;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.provider.Telephony;
38 import android.provider.Telephony.Carriers.EditStatus;
39 import android.telephony.SubscriptionManager;
40 import android.telephony.TelephonyFrameworkInitializer;
41 import android.telephony.TelephonyManager;
42 import android.text.TextUtils;
43 import android.util.Log;
44 
45 import com.android.internal.telephony.ITelephony;
46 
47 import java.io.PrintWriter;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.Executor;
52 import java.util.concurrent.TimeUnit;
53 import java.util.function.Supplier;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 
57 /**
58  * This class provides various util functions
59  */
60 public final class TelephonyUtils {
61     private static final String LOG_TAG = "TelephonyUtils";
62 
63     public static boolean IS_USER = "user".equals(android.os.Build.TYPE);
64     public static boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
65 
66     public static final Executor DIRECT_EXECUTOR = Runnable::run;
67 
68     /**
69      * Verify that caller holds {@link android.Manifest.permission#DUMP}.
70      *
71      * @return true if access should be granted.
72      */
checkDumpPermission(Context context, String tag, PrintWriter pw)73     public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
74         if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
75                 != PackageManager.PERMISSION_GRANTED) {
76             pw.println("Permission Denial: can't dump " + tag + " from from pid="
77                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
78                     + " due to missing android.permission.DUMP permission");
79             return false;
80         } else {
81             return true;
82         }
83     }
84 
85     /** Returns an empty string if the input is {@code null}. */
emptyIfNull(@ullable String str)86     public static String emptyIfNull(@Nullable String str) {
87         return str == null ? "" : str;
88     }
89 
90     /** Returns an empty list if the input is {@code null}. */
emptyIfNull(@ullable List<T> cur)91     public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
92         return cur == null ? Collections.emptyList() : cur;
93     }
94 
95     /**
96      * Returns a {@link ComponentInfo} from the {@link ResolveInfo},
97      * or throws an {@link IllegalStateException} if not available.
98      */
getComponentInfo(@onNull ResolveInfo resolveInfo)99     public static ComponentInfo getComponentInfo(@NonNull ResolveInfo resolveInfo) {
100         if (resolveInfo.activityInfo != null) return resolveInfo.activityInfo;
101         if (resolveInfo.serviceInfo != null) return resolveInfo.serviceInfo;
102         if (resolveInfo.providerInfo != null) return resolveInfo.providerInfo;
103         throw new IllegalStateException("Missing ComponentInfo!");
104     }
105 
106     /**
107      * Convenience method for running the provided action enclosed in
108      * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
109      *
110      * Any exception thrown by the given action will need to be handled by caller.
111      *
112      */
runWithCleanCallingIdentity( @onNull Runnable action)113     public static void runWithCleanCallingIdentity(
114             @NonNull Runnable action) {
115         final long callingIdentity = Binder.clearCallingIdentity();
116         try {
117             action.run();
118         } finally {
119             Binder.restoreCallingIdentity(callingIdentity);
120         }
121     }
122 
123     /**
124      * Convenience method for running the provided action in the provided
125      * executor enclosed in
126      * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
127      *
128      * Any exception thrown by the given action will need to be handled by caller.
129      *
130      */
runWithCleanCallingIdentity( @onNull Runnable action, @NonNull Executor executor)131     public static void runWithCleanCallingIdentity(
132             @NonNull Runnable action, @NonNull Executor executor) {
133         if (action != null) {
134             if (executor != null) {
135                 executor.execute(() -> runWithCleanCallingIdentity(action));
136             } else {
137                 runWithCleanCallingIdentity(action);
138             }
139         }
140     }
141 
142 
143     /**
144      * Convenience method for running the provided action enclosed in
145      * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} and return
146      * the result.
147      *
148      * Any exception thrown by the given action will need to be handled by caller.
149      *
150      */
runWithCleanCallingIdentity( @onNull Supplier<T> action)151     public static <T> T runWithCleanCallingIdentity(
152             @NonNull Supplier<T> action) {
153         final long callingIdentity = Binder.clearCallingIdentity();
154         try {
155             return action.get();
156         } finally {
157             Binder.restoreCallingIdentity(callingIdentity);
158         }
159     }
160 
161     /**
162      * Filter values in bundle to only basic types.
163      */
filterValues(Bundle bundle)164     public static Bundle filterValues(Bundle bundle) {
165         Bundle ret = new Bundle(bundle);
166         for (String key : bundle.keySet()) {
167             Object value = bundle.get(key);
168             if ((value instanceof Integer) || (value instanceof Long)
169                     || (value instanceof Double) || (value instanceof String)
170                     || (value instanceof int[]) || (value instanceof long[])
171                     || (value instanceof double[]) || (value instanceof String[])
172                     || (value instanceof PersistableBundle) || (value == null)
173                     || (value instanceof Boolean) || (value instanceof boolean[])) {
174                 continue;
175             }
176             if (value instanceof Bundle) {
177                 ret.putBundle(key, filterValues((Bundle) value));
178                 continue;
179             }
180             if (value.getClass().getName().startsWith("android.")) {
181                 continue;
182             }
183             ret.remove(key);
184         }
185         return ret;
186     }
187 
188     /** Wait for latch to trigger */
waitUntilReady(CountDownLatch latch, long timeoutMs)189     public static void waitUntilReady(CountDownLatch latch, long timeoutMs) {
190         try {
191             latch.await(timeoutMs, TimeUnit.MILLISECONDS);
192         } catch (InterruptedException ignored) {
193         }
194     }
195 
196     /**
197      * Convert data state to string
198      *
199      * @return The data state in string format.
200      */
dataStateToString(@ataState int state)201     public static String dataStateToString(@DataState int state) {
202         switch (state) {
203             case TelephonyManager.DATA_DISCONNECTED: return "DISCONNECTED";
204             case TelephonyManager.DATA_CONNECTING: return "CONNECTING";
205             case TelephonyManager.DATA_CONNECTED: return "CONNECTED";
206             case TelephonyManager.DATA_SUSPENDED: return "SUSPENDED";
207             case TelephonyManager.DATA_DISCONNECTING: return "DISCONNECTING";
208             case TelephonyManager.DATA_HANDOVER_IN_PROGRESS: return "HANDOVERINPROGRESS";
209             case TelephonyManager.DATA_UNKNOWN: return "UNKNOWN";
210         }
211         // This is the error case. The well-defined value for UNKNOWN is -1.
212         return "UNKNOWN(" + state + ")";
213     }
214 
215     /**
216      * Convert mobile data policy to string.
217      *
218      * @param mobileDataPolicy The mobile data policy.
219      * @return The mobile data policy in string format.
220      */
mobileDataPolicyToString( @elephonyManager.MobileDataPolicy int mobileDataPolicy)221     public static @NonNull String mobileDataPolicyToString(
222             @TelephonyManager.MobileDataPolicy int mobileDataPolicy) {
223         switch (mobileDataPolicy) {
224             case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
225                 return "DATA_ON_NON_DEFAULT_DURING_VOICE_CALL";
226             case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
227                 return "MMS_ALWAYS_ALLOWED";
228             case TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH:
229                 return "AUTO_DATA_SWITCH";
230             default:
231                 return "UNKNOWN(" + mobileDataPolicy + ")";
232         }
233     }
234 
235     /**
236      * Convert APN edited status to string.
237      *
238      * @param apnEditStatus APN edited status.
239      * @return APN edited status in string format.
240      */
apnEditedStatusToString(@ditStatus int apnEditStatus)241     public static @NonNull String apnEditedStatusToString(@EditStatus int apnEditStatus) {
242         return switch (apnEditStatus) {
243             case Telephony.Carriers.UNEDITED -> "UNEDITED";
244             case Telephony.Carriers.USER_EDITED -> "USER_EDITED";
245             case Telephony.Carriers.USER_DELETED -> "USER_DELETED";
246             case Telephony.Carriers.CARRIER_EDITED -> "CARRIER_EDITED";
247             case Telephony.Carriers.CARRIER_DELETED -> "CARRIER_DELETED";
248             default -> "UNKNOWN(" + apnEditStatus + ")";
249         };
250     }
251 
252     /**
253      * Utility method to get user handle associated with this subscription.
254      *
255      * This method should be used internally as it returns null instead of throwing
256      * IllegalArgumentException or IllegalStateException.
257      *
258      * @param context Context object
259      * @param subId the subId of the subscription.
260      * @return userHandle associated with this subscription
261      * or {@code null} if:
262      * 1. subscription is not associated with any user
263      * 2. subId is invalid.
264      * 3. subscription service is not available.
265      *
266      * @throws SecurityException if the caller doesn't have permissions required.
267      */
268     @Nullable
getSubscriptionUserHandle(Context context, int subId)269     public static UserHandle getSubscriptionUserHandle(Context context, int subId) {
270         UserHandle userHandle = null;
271         SubscriptionManager subManager =  context.getSystemService(SubscriptionManager.class);
272         if ((subManager != null) && (SubscriptionManager.isValidSubscriptionId(subId))) {
273             userHandle = subManager.getSubscriptionUserHandle(subId);
274         }
275         return userHandle;
276     }
277 
278     /**
279      * Show switch to managed profile dialog if subscription is associated with managed profile.
280      *
281      * @param context Context object
282      * @param subId subscription id
283      * @param callingUid uid for the calling app
284      * @param callingPackage package name of the calling app
285      */
showSwitchToManagedProfileDialogIfAppropriate(Context context, int subId, int callingUid, String callingPackage)286     public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
287             int subId, int callingUid, String callingPackage) {
288         final long token = Binder.clearCallingIdentity();
289         try {
290             UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
291             // We only want to show this dialog, while user actually trying to send the message from
292             // a messaging app, in other cases this dialog don't make sense.
293             if (!TelephonyUtils.isUidForeground(context, callingUid)
294                     || !TelephonyUtils.isPackageSMSRoleHolderForUser(context, callingPackage,
295                     callingUserHandle)) {
296                 return;
297             }
298 
299             SubscriptionManager subscriptionManager = context.getSystemService(
300                     SubscriptionManager.class);
301             if (!subscriptionManager.isActiveSubscriptionId(subId)) {
302                 Log.e(LOG_TAG, "Tried to send message with an inactive subscription " + subId);
303                 return;
304             }
305             UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
306             UserManager um = context.getSystemService(UserManager.class);
307 
308             if (associatedUserHandle != null && um.isManagedProfile(
309                     associatedUserHandle.getIdentifier())) {
310 
311                 ITelephony iTelephony = ITelephony.Stub.asInterface(
312                         TelephonyFrameworkInitializer
313                                 .getTelephonyServiceManager()
314                                 .getTelephonyServiceRegisterer()
315                                 .get());
316                 if (iTelephony != null) {
317                     try {
318                         iTelephony.showSwitchToManagedProfileDialog();
319                     } catch (RemoteException e) {
320                         Log.e(LOG_TAG, "Failed to launch switch to managed profile dialog.");
321                     }
322                 }
323             }
324         } finally {
325             Binder.restoreCallingIdentity(token);
326         }
327     }
328 
isUidForeground(Context context, int uid)329     private static boolean isUidForeground(Context context, int uid) {
330         ActivityManager am = context.getSystemService(ActivityManager.class);
331         boolean result = am != null && am.getUidImportance(uid)
332                 == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
333         return result;
334     }
335 
isPackageSMSRoleHolderForUser(Context context, String callingPackage, UserHandle user)336     private static boolean isPackageSMSRoleHolderForUser(Context context, String callingPackage,
337             UserHandle user) {
338         RoleManager roleManager = context.getSystemService(RoleManager.class);
339         final List<String> smsRoleHolder = roleManager.getRoleHoldersAsUser(
340                 RoleManager.ROLE_SMS, user);
341 
342         // ROLE_SMS is an exclusive role per user, so there would just be one entry in the
343         // retuned list if not empty
344         if (!smsRoleHolder.isEmpty() && callingPackage.equals(smsRoleHolder.get(0))) {
345             return true;
346         }
347         return false;
348 
349     }
350 
351     /**
352      * @param input string that want to be compared.
353      * @param regex string that express regular expression
354      * @return {@code true} if matched  {@code false} otherwise.
355      */
isValidPattern(@ullable String input, @Nullable String regex)356     private static boolean isValidPattern(@Nullable String input, @Nullable String regex) {
357         if (TextUtils.isEmpty(input) || TextUtils.isEmpty(regex)) {
358             return false;
359         }
360         Pattern pattern = Pattern.compile(regex);
361         Matcher matcher = pattern.matcher(input);
362         if (!matcher.matches()) {
363             return false;
364         }
365         return true;
366     }
367 
368     /**
369      * @param countryCode two letters country code based on the ISO 3166-1.
370      * @return {@code true} if the countryCode is valid {@code false} otherwise.
371      */
isValidCountryCode(@ullable String countryCode)372     public static boolean isValidCountryCode(@Nullable String countryCode) {
373         return isValidPattern(countryCode, "^[A-Za-z]{2}$");
374     }
375 
376     /**
377      * @param plmn target plmn for validation.
378      * @return {@code true} if the target plmn is valid {@code false} otherwise.
379      */
isValidPlmn(@ullable String plmn)380     public static boolean isValidPlmn(@Nullable String plmn) {
381         return isValidPattern(plmn, "^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
382     }
383 
384     /**
385      * @param serviceType target serviceType for validation.
386      * @return {@code true} if the target serviceType is valid {@code false} otherwise.
387      */
isValidService(int serviceType)388     public static boolean isValidService(int serviceType) {
389         if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) {
390             return false;
391         }
392         return true;
393     }
394 }
395