1 /* 2 * Copyright (C) 2018 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.settings.network.telephony; 18 19 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; 20 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.database.Cursor; 27 import android.graphics.Color; 28 import android.graphics.drawable.ColorDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.LayerDrawable; 31 import android.os.PersistableBundle; 32 import android.os.SystemProperties; 33 import android.provider.Settings; 34 import android.telecom.PhoneAccountHandle; 35 import android.telecom.TelecomManager; 36 import android.telephony.CarrierConfigManager; 37 import android.telephony.SubscriptionInfo; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.TelephonyManager; 40 import android.telephony.euicc.EuiccManager; 41 import android.telephony.ims.feature.ImsFeature; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.Gravity; 45 46 import androidx.annotation.VisibleForTesting; 47 48 import com.android.ims.ImsException; 49 import com.android.ims.ImsManager; 50 import com.android.internal.telephony.Phone; 51 import com.android.internal.telephony.PhoneConstants; 52 import com.android.internal.util.ArrayUtils; 53 import com.android.settings.R; 54 import com.android.settings.Utils; 55 import com.android.settings.core.BasePreferenceController; 56 import com.android.settingslib.graph.SignalDrawable; 57 58 import java.util.Arrays; 59 import java.util.List; 60 61 public class MobileNetworkUtils { 62 63 private static final String TAG = "MobileNetworkUtils"; 64 65 // CID of the device. 66 private static final String KEY_CID = "ro.boot.cid"; 67 // CIDs of devices which should not show anything related to eSIM. 68 private static final String KEY_ESIM_CID_IGNORE = "ro.setupwizard.esim_cid_ignore"; 69 // System Property which is used to decide whether the default eSIM UI will be shown, 70 // the default value is false. 71 private static final String KEY_ENABLE_ESIM_UI_BY_DEFAULT = 72 "esim.enable_esim_system_ui_by_default"; 73 private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT = 74 "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; 75 76 // The following constants are used to draw signal icon. 77 public static final int NO_CELL_DATA_TYPE_ICON = 0; 78 public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); 79 80 /** 81 * Returns if DPC APNs are enforced. 82 */ isDpcApnEnforced(Context context)83 public static boolean isDpcApnEnforced(Context context) { 84 try (Cursor enforceCursor = context.getContentResolver().query(ENFORCE_MANAGED_URI, 85 null, null, null, null)) { 86 if (enforceCursor == null || enforceCursor.getCount() != 1) { 87 return false; 88 } 89 enforceCursor.moveToFirst(); 90 return enforceCursor.getInt(0) > 0; 91 } 92 } 93 94 /** 95 * Returns true if Wifi calling is enabled for at least one subscription. 96 */ isWifiCallingEnabled(Context context)97 public static boolean isWifiCallingEnabled(Context context) { 98 SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class); 99 if (subManager == null) { 100 Log.e(TAG, "isWifiCallingEnabled: couldn't get system service."); 101 return false; 102 } 103 for (int subId : subManager.getActiveSubscriptionIdList()) { 104 if (isWifiCallingEnabled(context, subId)) { 105 return true; 106 } 107 } 108 return false; 109 } 110 111 /** 112 * Returns true if Wifi calling is enabled for the specific subscription with id {@code subId}. 113 */ isWifiCallingEnabled(Context context, int subId)114 public static boolean isWifiCallingEnabled(Context context, int subId) { 115 final PhoneAccountHandle simCallManager = 116 TelecomManager.from(context).getSimCallManagerForSubscription(subId); 117 final int phoneId = SubscriptionManager.getSlotIndex(subId); 118 119 boolean isWifiCallingEnabled; 120 if (simCallManager != null) { 121 Intent intent = buildPhoneAccountConfigureIntent( 122 context, simCallManager); 123 124 isWifiCallingEnabled = intent != null; 125 } else { 126 ImsManager imsMgr = ImsManager.getInstance(context, phoneId); 127 isWifiCallingEnabled = imsMgr != null 128 && imsMgr.isWfcEnabledByPlatform() 129 && imsMgr.isWfcProvisionedOnDevice() 130 && isImsServiceStateReady(imsMgr); 131 } 132 133 return isWifiCallingEnabled; 134 } 135 136 @VisibleForTesting buildPhoneAccountConfigureIntent( Context context, PhoneAccountHandle accountHandle)137 static Intent buildPhoneAccountConfigureIntent( 138 Context context, PhoneAccountHandle accountHandle) { 139 Intent intent = buildConfigureIntent( 140 context, accountHandle, TelecomManager.ACTION_CONFIGURE_PHONE_ACCOUNT); 141 142 if (intent == null) { 143 // If the new configuration didn't work, try the old configuration intent. 144 intent = buildConfigureIntent(context, accountHandle, 145 LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT); 146 } 147 return intent; 148 } 149 buildConfigureIntent( Context context, PhoneAccountHandle accountHandle, String actionStr)150 private static Intent buildConfigureIntent( 151 Context context, PhoneAccountHandle accountHandle, String actionStr) { 152 if (accountHandle == null || accountHandle.getComponentName() == null 153 || TextUtils.isEmpty(accountHandle.getComponentName().getPackageName())) { 154 return null; 155 } 156 157 // Build the settings intent. 158 Intent intent = new Intent(actionStr); 159 intent.setPackage(accountHandle.getComponentName().getPackageName()); 160 intent.addCategory(Intent.CATEGORY_DEFAULT); 161 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); 162 163 // Check to see that the phone account package can handle the setting intent. 164 PackageManager pm = context.getPackageManager(); 165 List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0); 166 if (resolutions.size() == 0) { 167 intent = null; // set no intent if the package cannot handle it. 168 } 169 170 return intent; 171 } 172 isImsServiceStateReady(ImsManager imsMgr)173 public static boolean isImsServiceStateReady(ImsManager imsMgr) { 174 boolean isImsServiceStateReady = false; 175 176 try { 177 if (imsMgr != null && imsMgr.getImsServiceState() == ImsFeature.STATE_READY) { 178 isImsServiceStateReady = true; 179 } 180 } catch (ImsException ex) { 181 Log.e(TAG, "Exception when trying to get ImsServiceStatus: " + ex); 182 } 183 184 Log.d(TAG, "isImsServiceStateReady=" + isImsServiceStateReady); 185 return isImsServiceStateReady; 186 } 187 188 /** 189 * Whether to show the entry point to eUICC settings. 190 * 191 * <p>We show the entry point on any device which supports eUICC as long as either the eUICC 192 * was ever provisioned (that is, at least one profile was ever downloaded onto it), or if 193 * the user has enabled development mode. 194 */ showEuiccSettings(Context context)195 public static boolean showEuiccSettings(Context context) { 196 EuiccManager euiccManager = 197 (EuiccManager) context.getSystemService(EuiccManager.class); 198 if (!euiccManager.isEnabled()) { 199 return false; 200 } 201 202 final ContentResolver cr = context.getContentResolver(); 203 204 TelephonyManager tm = 205 (TelephonyManager) context.getSystemService(TelephonyManager.class); 206 String currentCountry = tm.getNetworkCountryIso().toLowerCase(); 207 String supportedCountries = 208 Settings.Global.getString(cr, Settings.Global.EUICC_SUPPORTED_COUNTRIES); 209 boolean inEsimSupportedCountries = false; 210 if (TextUtils.isEmpty(currentCountry)) { 211 inEsimSupportedCountries = true; 212 } else if (!TextUtils.isEmpty(supportedCountries)) { 213 List<String> supportedCountryList = 214 Arrays.asList(TextUtils.split(supportedCountries.toLowerCase(), ",")); 215 if (supportedCountryList.contains(currentCountry)) { 216 inEsimSupportedCountries = true; 217 } 218 } 219 final boolean esimIgnoredDevice = 220 Arrays.asList(TextUtils.split(SystemProperties.get(KEY_ESIM_CID_IGNORE, ""), ",")) 221 .contains(SystemProperties.get(KEY_CID, null)); 222 final boolean enabledEsimUiByDefault = 223 SystemProperties.getBoolean(KEY_ENABLE_ESIM_UI_BY_DEFAULT, true); 224 final boolean euiccProvisioned = 225 Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0; 226 final boolean inDeveloperMode = 227 Settings.Global.getInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; 228 229 return (inDeveloperMode || euiccProvisioned 230 || (!esimIgnoredDevice && enabledEsimUiByDefault && inEsimSupportedCountries)); 231 } 232 233 /** 234 * Set whether to enable data for {@code subId}, also whether to disable data for other 235 * subscription 236 */ setMobileDataEnabled(Context context, int subId, boolean enabled, boolean disableOtherSubscriptions)237 public static void setMobileDataEnabled(Context context, int subId, boolean enabled, 238 boolean disableOtherSubscriptions) { 239 final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) 240 .createForSubscriptionId(subId); 241 final SubscriptionManager subscriptionManager = context.getSystemService( 242 SubscriptionManager.class); 243 telephonyManager.setDataEnabled(enabled); 244 245 if (disableOtherSubscriptions) { 246 List<SubscriptionInfo> subInfoList = 247 subscriptionManager.getActiveSubscriptionInfoList(true); 248 if (subInfoList != null) { 249 for (SubscriptionInfo subInfo : subInfoList) { 250 // We never disable mobile data for opportunistic subscriptions. 251 if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) { 252 context.getSystemService(TelephonyManager.class).createForSubscriptionId( 253 subInfo.getSubscriptionId()).setDataEnabled(false); 254 } 255 } 256 } 257 } 258 } 259 260 /** 261 * Return {@code true} if show CDMA category 262 */ isCdmaOptions(Context context, int subId)263 public static boolean isCdmaOptions(Context context, int subId) { 264 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 265 return false; 266 } 267 final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) 268 .createForSubscriptionId(subId); 269 final PersistableBundle carrierConfig = context.getSystemService( 270 CarrierConfigManager.class).getConfigForSubId(subId); 271 272 273 if (telephonyManager.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 274 return true; 275 } else if (carrierConfig != null 276 && !carrierConfig.getBoolean( 277 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) 278 && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { 279 return true; 280 } 281 282 if (isWorldMode(context, subId)) { 283 final int settingsNetworkMode = android.provider.Settings.Global.getInt( 284 context.getContentResolver(), 285 android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId, 286 Phone.PREFERRED_NT_MODE); 287 if (settingsNetworkMode == TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA 288 || settingsNetworkMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO) { 289 return true; 290 } 291 292 if (shouldSpeciallyUpdateGsmCdma(context, subId)) { 293 return true; 294 } 295 } 296 297 return false; 298 } 299 300 /** 301 * return {@code true} if we need show Gsm related settings 302 */ isGsmOptions(Context context, int subId)303 public static boolean isGsmOptions(Context context, int subId) { 304 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 305 return false; 306 } 307 if (isGsmBasicOptions(context, subId)) { 308 return true; 309 } 310 final int networkMode = android.provider.Settings.Global.getInt( 311 context.getContentResolver(), 312 android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId, 313 Phone.PREFERRED_NT_MODE); 314 if (isWorldMode(context, subId)) { 315 if (networkMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO 316 || networkMode == TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA) { 317 return true; 318 } else if (shouldSpeciallyUpdateGsmCdma(context, subId)) { 319 return true; 320 } 321 } 322 323 return false; 324 } 325 isGsmBasicOptions(Context context, int subId)326 private static boolean isGsmBasicOptions(Context context, int subId) { 327 final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) 328 .createForSubscriptionId(subId); 329 final PersistableBundle carrierConfig = context.getSystemService( 330 CarrierConfigManager.class).getConfigForSubId(subId); 331 332 if (telephonyManager.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 333 return true; 334 } else if (carrierConfig != null 335 && !carrierConfig.getBoolean( 336 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) 337 && carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_PHONE_BOOL)) { 338 return true; 339 } 340 341 return false; 342 } 343 344 /** 345 * Return {@code true} if it is world mode, and we may show advanced options in telephony 346 * settings 347 */ isWorldMode(Context context, int subId)348 public static boolean isWorldMode(Context context, int subId) { 349 final PersistableBundle carrierConfig = context.getSystemService( 350 CarrierConfigManager.class).getConfigForSubId(subId); 351 return carrierConfig == null 352 ? false 353 : carrierConfig.getBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL); 354 } 355 356 /** 357 * Return {@code true} if we need show settings for network selection(i.e. Verizon) 358 */ shouldDisplayNetworkSelectOptions(Context context, int subId)359 public static boolean shouldDisplayNetworkSelectOptions(Context context, int subId) { 360 final TelephonyManager telephonyManager = TelephonyManager.from(context) 361 .createForSubscriptionId(subId); 362 final PersistableBundle carrierConfig = context.getSystemService( 363 CarrierConfigManager.class).getConfigForSubId(subId); 364 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID 365 || carrierConfig == null 366 || !carrierConfig.getBoolean( 367 CarrierConfigManager.KEY_OPERATOR_SELECTION_EXPAND_BOOL) 368 || carrierConfig.getBoolean( 369 CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL) 370 || (carrierConfig.getBoolean(CarrierConfigManager.KEY_CSP_ENABLED_BOOL) 371 && !telephonyManager.isManualNetworkSelectionAllowed())) { 372 return false; 373 } 374 375 final int networkMode = android.provider.Settings.Global.getInt( 376 context.getContentResolver(), 377 android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId, 378 Phone.PREFERRED_NT_MODE); 379 if (networkMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO 380 && isWorldMode(context, subId)) { 381 return false; 382 } 383 if (shouldSpeciallyUpdateGsmCdma(context, subId)) { 384 return false; 385 } 386 387 if (isGsmBasicOptions(context, subId)) { 388 return true; 389 } 390 391 if (isWorldMode(context, subId)) { 392 if (networkMode == TelephonyManager.NETWORK_MODE_LTE_GSM_WCDMA) { 393 return true; 394 } 395 } 396 397 return false; 398 } 399 400 /** 401 * Return {@code true} if Tdscdma is supported in current subscription 402 */ isTdscdmaSupported(Context context, int subId)403 public static boolean isTdscdmaSupported(Context context, int subId) { 404 return isTdscdmaSupported(context, 405 context.getSystemService(TelephonyManager.class).createForSubscriptionId(subId)); 406 } 407 408 //TODO(b/117651939): move it to telephony isTdscdmaSupported(Context context, TelephonyManager telephonyManager)409 private static boolean isTdscdmaSupported(Context context, TelephonyManager telephonyManager) { 410 final PersistableBundle carrierConfig = context.getSystemService( 411 CarrierConfigManager.class).getConfig(); 412 413 if (carrierConfig == null) { 414 return false; 415 } 416 417 if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SUPPORT_TDSCDMA_BOOL)) { 418 return true; 419 } 420 421 String operatorNumeric = telephonyManager.getServiceState().getOperatorNumeric(); 422 String[] numericArray = carrierConfig.getStringArray( 423 CarrierConfigManager.KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY); 424 if (numericArray == null || operatorNumeric == null) { 425 return false; 426 } 427 for (String numeric : numericArray) { 428 if (operatorNumeric.equals(numeric)) { 429 return true; 430 } 431 } 432 return false; 433 } 434 435 /** 436 * Return subId that supported by search. If there are more than one, return first one, 437 * otherwise return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} 438 */ getSearchableSubscriptionId(Context context)439 public static int getSearchableSubscriptionId(Context context) { 440 final SubscriptionManager subscriptionManager = context.getSystemService( 441 SubscriptionManager.class); 442 final int subIds[] = subscriptionManager.getActiveSubscriptionIdList(); 443 444 return subIds.length >= 1 ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID; 445 } 446 447 /** 448 * Return availability for a default subscription id. If subId already been set, use it to 449 * check, otherwise traverse all active subIds on device to check. 450 * @param context context 451 * @param defSubId Default subId get from telephony preference controller 452 * @param callback Callback to check availability for a specific subId 453 * @return Availability 454 * 455 * @see BasePreferenceController#getAvailabilityStatus() 456 */ getAvailability(Context context, int defSubId, TelephonyAvailabilityCallback callback)457 public static int getAvailability(Context context, int defSubId, 458 TelephonyAvailabilityCallback callback) { 459 final SubscriptionManager subscriptionManager = context.getSystemService( 460 SubscriptionManager.class); 461 if (defSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 462 // If subId has been set, return the corresponding status 463 return callback.getAvailabilityStatus(defSubId); 464 } else { 465 // Otherwise, search whether there is one subId in device that support this preference 466 final int[] subIds = subscriptionManager.getActiveSubscriptionIdList(); 467 if (ArrayUtils.isEmpty(subIds)) { 468 return callback.getAvailabilityStatus( 469 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 470 } else { 471 for (final int subId : subIds) { 472 final int status = callback.getAvailabilityStatus(subId); 473 if (status == BasePreferenceController.AVAILABLE) { 474 return status; 475 } 476 } 477 return callback.getAvailabilityStatus(subIds[0]); 478 } 479 } 480 } 481 482 /** 483 * This method is migrated from {@link com.android.phone.MobileNetworkSettings} and we should 484 * use it carefully. This code snippet doesn't have very clear meaning however we should 485 * update GSM or CDMA differently based on what it returns. 486 * 487 * 1. For all CDMA settings, make them visible if it return {@code true} 488 * 2. For GSM settings, make them visible if it return {@code true} unless 3 489 * 3. For network select settings, make it invisible if it return {@code true} 490 */ 491 @VisibleForTesting shouldSpeciallyUpdateGsmCdma(Context context, int subId)492 static boolean shouldSpeciallyUpdateGsmCdma(Context context, int subId) { 493 final int networkMode = android.provider.Settings.Global.getInt( 494 context.getContentResolver(), 495 android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId, 496 Phone.PREFERRED_NT_MODE); 497 if (networkMode == TelephonyManager.NETWORK_MODE_LTE_TDSCDMA_GSM 498 || networkMode == TelephonyManager.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA 499 || networkMode == TelephonyManager.NETWORK_MODE_LTE_TDSCDMA 500 || networkMode == TelephonyManager.NETWORK_MODE_LTE_TDSCDMA_WCDMA 501 || networkMode == TelephonyManager.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA 502 || networkMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) { 503 if (!isTdscdmaSupported(context, subId) && isWorldMode(context, subId)) { 504 return true; 505 } 506 } 507 508 return false; 509 } 510 getSignalStrengthIcon(Context context, int level, int numLevels, int iconType, boolean cutOut)511 public static Drawable getSignalStrengthIcon(Context context, int level, int numLevels, 512 int iconType, boolean cutOut) { 513 SignalDrawable signalDrawable = new SignalDrawable(context); 514 signalDrawable.setLevel( 515 SignalDrawable.getState(level, numLevels, cutOut)); 516 517 // Make the network type drawable 518 Drawable networkDrawable = 519 iconType == NO_CELL_DATA_TYPE_ICON 520 ? EMPTY_DRAWABLE 521 : context 522 .getResources().getDrawable(iconType, context.getTheme()); 523 524 // Overlay the two drawables 525 final Drawable[] layers = {networkDrawable, signalDrawable}; 526 final int iconSize = 527 context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size); 528 529 LayerDrawable icons = new LayerDrawable(layers); 530 // Set the network type icon at the top left 531 icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT); 532 // Set the signal strength icon at the bottom right 533 icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT); 534 icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize); 535 icons.setTintList(Utils.getColorAttr(context, android.R.attr.colorControlNormal)); 536 return icons; 537 } 538 } 539