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