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