1 package com.android.phone.settings; 2 3 import android.content.ComponentName; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.pm.PackageManager; 7 import android.content.pm.ResolveInfo; 8 import android.graphics.drawable.Icon; 9 import android.os.Binder; 10 import android.os.Bundle; 11 import android.os.UserHandle; 12 import android.os.UserManager; 13 import android.preference.Preference; 14 import android.preference.PreferenceCategory; 15 import android.preference.PreferenceFragment; 16 import android.telecom.PhoneAccount; 17 import android.telecom.PhoneAccountHandle; 18 import android.telecom.TelecomManager; 19 import android.telephony.CarrierConfigManager; 20 import android.telephony.SubscriptionInfo; 21 import android.telephony.SubscriptionManager; 22 import android.telephony.TelephonyManager; 23 import android.text.TextUtils; 24 import android.util.Log; 25 26 import com.android.internal.telephony.Phone; 27 import com.android.internal.telephony.flags.Flags; 28 import com.android.phone.PhoneUtils; 29 import com.android.phone.R; 30 import com.android.phone.SubscriptionInfoHelper; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.stream.Collectors; 38 39 public class PhoneAccountSettingsFragment extends PreferenceFragment 40 implements Preference.OnPreferenceChangeListener, 41 AccountSelectionPreference.AccountSelectionListener { 42 43 private static final String ACCOUNTS_LIST_CATEGORY_KEY = 44 "phone_accounts_accounts_list_category_key"; 45 46 private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_accounts_all_calling_accounts"; 47 48 private static final String MAKE_AND_RECEIVE_CALLS_CATEGORY_KEY = 49 "make_and_receive_calls_settings_category_key"; 50 private static final String DEFAULT_OUTGOING_ACCOUNT_KEY = "default_outgoing_account"; 51 private static final String SMART_FORWARDING_CONFIGURATION_PREF_KEY = 52 "smart_forwarding_configuration_key"; 53 54 private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT = 55 "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; 56 57 /** 58 * Value to start ordering of phone accounts relative to other preferences. By setting this 59 * value on the phone account listings, we ensure that anything that is ordered before 60 * {value} in the preference XML comes before the phone account list and anything with 61 * a value significantly larger will list after. 62 */ 63 private static final int ACCOUNT_ORDERING_START_VALUE = 100; 64 65 private static final String LOG_TAG = PhoneAccountSettingsFragment.class.getSimpleName(); 66 67 private TelecomManager mTelecomManager; 68 private TelephonyManager mTelephonyManager; 69 private SubscriptionManager mSubscriptionManager; 70 71 private PreferenceCategory mAccountList; 72 73 private AccountSelectionPreference mDefaultOutgoingAccount; 74 private Preference mAllCallingAccounts; 75 76 private PreferenceCategory mMakeAndReceiveCallsCategory; 77 private boolean mMakeAndReceiveCallsCategoryPresent; 78 79 private final SubscriptionManager.OnSubscriptionsChangedListener 80 mOnSubscriptionsChangeListener = 81 new SubscriptionManager.OnSubscriptionsChangedListener() { 82 @Override 83 public void onSubscriptionsChanged() { 84 if (getActivity() == null) { 85 return; 86 } 87 updateAccounts(); 88 } 89 }; 90 91 @Override onCreate(Bundle icicle)92 public void onCreate(Bundle icicle) { 93 super.onCreate(icicle); 94 95 mTelecomManager = getActivity().getSystemService(TelecomManager.class); 96 mTelephonyManager = TelephonyManager.from(getActivity()); 97 mSubscriptionManager = SubscriptionManager.from(getActivity()); 98 if (Flags.workProfileApiSplit()) { 99 mSubscriptionManager = mSubscriptionManager.createForAllUserProfiles(); 100 } 101 } 102 103 @Override onResume()104 public void onResume() { 105 super.onResume(); 106 107 if (getPreferenceScreen() != null) { 108 getPreferenceScreen().removeAll(); 109 } 110 111 addPreferencesFromResource(R.xml.phone_account_settings); 112 113 /** 114 * Here we make decisions about what we will and will not display with regards to phone- 115 * account settings. The basic settings structure is this: 116 * (1) <Make Calls With...> // Lets user pick a default account for outgoing calls 117 * (2) <Account List> 118 * <Account> 119 * ... 120 * <Account> 121 * </Account List> 122 * (3) <All Accounts> // Lets user enable/disable third-party accounts. SIM-based accounts 123 * // are always enabled and so aren't relevant here. 124 * 125 * Here are the rules that we follow: 126 * - (1) is only shown if there are multiple enabled accounts, including SIM accounts. 127 * This can be 2+ SIM accounts, 2+ third party accounts or any combination. 128 * - (2) The account list only lists (a) enabled third party accounts and (b) SIM-based 129 * accounts. However, for single-SIM devices, if the only account to show is the 130 * SIM-based account, we don't show the list at all under the assumption that the user 131 * already knows about the account. 132 * - (3) Is only shown if there exist any third party accounts. If none exist, then the 133 * option is hidden since there is nothing that can be done in it. 134 * 135 * By far, the most common case for users will be the single-SIM device without any 136 * third party accounts. IOW, the great majority of users won't see any of these options. 137 */ 138 mAccountList = (PreferenceCategory) getPreferenceScreen().findPreference( 139 ACCOUNTS_LIST_CATEGORY_KEY); 140 mDefaultOutgoingAccount = (AccountSelectionPreference) 141 getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY); 142 mAllCallingAccounts = getPreferenceScreen().findPreference(ALL_CALLING_ACCOUNTS_KEY); 143 144 mMakeAndReceiveCallsCategory = (PreferenceCategory) getPreferenceScreen().findPreference( 145 MAKE_AND_RECEIVE_CALLS_CATEGORY_KEY); 146 mMakeAndReceiveCallsCategoryPresent = false; 147 148 updateAccounts(); 149 updateMakeCallsOptions(); 150 151 SubscriptionManager.from(getActivity()).addOnSubscriptionsChangedListener( 152 mOnSubscriptionsChangeListener); 153 } 154 155 @Override onPause()156 public void onPause() { 157 SubscriptionManager.from(getActivity()).removeOnSubscriptionsChangedListener( 158 mOnSubscriptionsChangeListener); 159 super.onPause(); 160 } 161 162 /** 163 * Handles changes to the preferences. 164 * 165 * @param pref The preference changed. 166 * @param objValue The changed value. 167 * @return True if the preference change has been handled, and false otherwise. 168 */ 169 @Override onPreferenceChange(Preference pref, Object objValue)170 public boolean onPreferenceChange(Preference pref, Object objValue) { 171 return false; 172 } 173 174 /** 175 * Handles a phone account selection for the default outgoing phone account. 176 * 177 * @param pref The account selection preference which triggered the account selected event. 178 * @param account The account selected. 179 * @return True if the account selection has been handled, and false otherwise. 180 */ 181 @Override onAccountSelected(AccountSelectionPreference pref, PhoneAccountHandle account)182 public boolean onAccountSelected(AccountSelectionPreference pref, PhoneAccountHandle account) { 183 Log.d(LOG_TAG, String.format("onAccountSelected: pref=[%s], account=[%s]", pref, account)); 184 if (pref == mDefaultOutgoingAccount) { 185 mTelecomManager.setUserSelectedOutgoingPhoneAccount(account); 186 return true; 187 } 188 return false; 189 } 190 191 /** 192 * Repopulate the dialog to pick up changes before showing. 193 * 194 * @param pref The account selection preference dialog being shown. 195 */ 196 @Override onAccountSelectionDialogShow(AccountSelectionPreference pref)197 public void onAccountSelectionDialogShow(AccountSelectionPreference pref) { 198 if (pref == mDefaultOutgoingAccount) { 199 updateDefaultOutgoingAccountsModel(); 200 } 201 } 202 203 @Override onAccountChanged(AccountSelectionPreference pref)204 public void onAccountChanged(AccountSelectionPreference pref) {} 205 206 /** 207 * Queries the telcomm manager to update the default outgoing account selection preference 208 * with the list of outgoing accounts and the current default outgoing account. 209 */ updateDefaultOutgoingAccountsModel()210 private void updateDefaultOutgoingAccountsModel() { 211 mDefaultOutgoingAccount.setModel( 212 mTelecomManager, 213 getCallingAccounts(true /* includeSims */, false /* includeDisabled */), 214 mTelecomManager.getUserSelectedOutgoingPhoneAccount(), 215 getString(R.string.phone_accounts_ask_every_time)); 216 } 217 initAccountList(List<PhoneAccountHandle> enabledAccounts)218 private void initAccountList(List<PhoneAccountHandle> enabledAccounts) { 219 220 boolean isMultiSimDevice = mTelephonyManager.isMultiSimEnabled(); 221 222 // On a single-SIM device, do not list any accounts if the only account is the SIM-based 223 // one. This is because on single-SIM devices, we do not expose SIM settings through the 224 // account listing entry so showing it does nothing to help the user. Nor does the lack of 225 // action match the "Settings" header above the listing. 226 if (!isMultiSimDevice && getCallingAccounts( 227 false /* includeSims */, false /* includeDisabled */).isEmpty()){ 228 return; 229 } 230 231 // Obtain the list of phone accounts. 232 List<PhoneAccount> accounts = new ArrayList<>(); 233 for (PhoneAccountHandle handle : enabledAccounts) { 234 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 235 if (account != null) { 236 accounts.add(account); 237 } 238 } 239 240 // Sort the accounts according to how we want to display them. 241 Collections.sort(accounts, new Comparator<PhoneAccount>() { 242 @Override 243 public int compare(PhoneAccount account1, PhoneAccount account2) { 244 int retval = 0; 245 246 // SIM accounts go first 247 boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 248 boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 249 if (isSim1 != isSim2) { 250 retval = isSim1 ? -1 : 1; 251 } 252 253 int subId1 = mTelephonyManager.getSubIdForPhoneAccount(account1); 254 int subId2 = mTelephonyManager.getSubIdForPhoneAccount(account2); 255 if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID && 256 subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 257 retval = (mSubscriptionManager.getSlotIndex(subId1) < 258 mSubscriptionManager.getSlotIndex(subId2)) ? -1 : 1; 259 } 260 261 // Then order by package 262 if (retval == 0) { 263 String pkg1 = account1.getAccountHandle().getComponentName().getPackageName(); 264 String pkg2 = account2.getAccountHandle().getComponentName().getPackageName(); 265 retval = pkg1.compareTo(pkg2); 266 } 267 268 // Finally, order by label 269 if (retval == 0) { 270 String label1 = nullToEmpty(account1.getLabel().toString()); 271 String label2 = nullToEmpty(account2.getLabel().toString()); 272 retval = label1.compareTo(label2); 273 } 274 275 // Then by hashcode 276 if (retval == 0) { 277 retval = account1.hashCode() - account2.hashCode(); 278 } 279 return retval; 280 } 281 }); 282 283 int order = ACCOUNT_ORDERING_START_VALUE; 284 285 // Add an entry for each account. 286 for (PhoneAccount account : accounts) { 287 PhoneAccountHandle handle = account.getAccountHandle(); 288 Intent intent = null; 289 290 // SIM phone accounts use a different setting intent and are thus handled differently. 291 if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 292 293 // For SIM-based accounts, we only expose the settings through the account list 294 // if we are on a multi-SIM device. For single-SIM devices, the settings are 295 // more spread out so there is no good single place to take the user, so we don't. 296 if (isMultiSimDevice) { 297 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo( 298 mTelephonyManager.getSubIdForPhoneAccount(account)); 299 300 if (subInfo != null) { 301 intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS); 302 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 303 SubscriptionInfoHelper.addExtrasToIntent(intent, subInfo); 304 } 305 } 306 } else { 307 intent = buildPhoneAccountConfigureIntent(getActivity(), handle); 308 } 309 310 // Create the preference & add the label 311 Preference accountPreference = new Preference(getActivity()); 312 CharSequence accountLabel = account.getLabel(); 313 boolean isSimAccount = 314 account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 315 accountPreference.setTitle((TextUtils.isEmpty(accountLabel) && isSimAccount) 316 ? getString(R.string.phone_accounts_default_account_label) : accountLabel); 317 318 // Add an icon. 319 Icon icon = account.getIcon(); 320 if (icon != null) { 321 accountPreference.setIcon(icon.loadDrawable(getActivity())); 322 } 323 324 // Add an intent to send the user to the account's settings. 325 if (intent != null) { 326 accountPreference.setIntent(intent); 327 } 328 329 accountPreference.setOrder(order++); 330 mAccountList.addPreference(accountPreference); 331 } 332 } 333 shouldShowConnectionServiceList(List<PhoneAccountHandle> allNonSimAccounts)334 private boolean shouldShowConnectionServiceList(List<PhoneAccountHandle> allNonSimAccounts) { 335 return mTelephonyManager.isMultiSimEnabled() || allNonSimAccounts.size() > 0; 336 } 337 updateAccounts()338 private void updateAccounts() { 339 if (mAccountList != null) { 340 mAccountList.removeAll(); 341 List<PhoneAccountHandle> allNonSimAccounts = 342 getCallingAccounts(false /* includeSims */, true /* includeDisabled */); 343 344 List<PhoneAccountHandle> enabledAccounts = 345 getCallingAccounts(true /* includeSims */, false /* includeDisabled */); 346 // Initialize the account list with the set of enabled & SIM accounts. 347 initAccountList(enabledAccounts); 348 349 // Always show the 'Make Calls With..." option 350 mMakeAndReceiveCallsCategory.addPreference(mDefaultOutgoingAccount); 351 mMakeAndReceiveCallsCategoryPresent = true; 352 mDefaultOutgoingAccount.setListener(this); 353 updateDefaultOutgoingAccountsModel(); 354 355 // If there are no third party (nonSim) accounts, 356 // then don't show enable/disable dialog. 357 if (!allNonSimAccounts.isEmpty()) { 358 mAccountList.addPreference(mAllCallingAccounts); 359 } else { 360 mAccountList.removePreference(mAllCallingAccounts); 361 } 362 } 363 } 364 getCallingAccounts( boolean includeSims, boolean includeDisabledAccounts)365 private List<PhoneAccountHandle> getCallingAccounts( 366 boolean includeSims, boolean includeDisabledAccounts) { 367 PhoneAccountHandle emergencyAccountHandle = getEmergencyPhoneAccount(); 368 369 List<PhoneAccountHandle> accountHandles = 370 mTelecomManager.getCallCapablePhoneAccounts(includeDisabledAccounts); 371 for (Iterator<PhoneAccountHandle> i = accountHandles.iterator(); i.hasNext();) { 372 PhoneAccountHandle handle = i.next(); 373 UserHandle userHandle = handle.getUserHandle(); 374 if (handle.equals(emergencyAccountHandle)) { 375 // never include emergency call accounts in this piece of code. 376 i.remove(); 377 continue; 378 } 379 380 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 381 if (account == null) { 382 i.remove(); 383 } else if (!includeSims && 384 account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 385 i.remove(); 386 } else if (!userHandle.equals(Binder.getCallingUserHandle()) 387 && !account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 388 // Only show accounts for the current user (unless account has 389 // CAPABILITY_MULTI_USER). 390 i.remove(); 391 } 392 } 393 return accountHandles; 394 } 395 nullToEmpty(String str)396 private String nullToEmpty(String str) { 397 return str == null ? "" : str; 398 } 399 getEmergencyPhoneAccount()400 private PhoneAccountHandle getEmergencyPhoneAccount() { 401 return PhoneUtils.makePstnPhoneAccountHandleWithPrefix( 402 (Phone) null, "" /* prefix */, true /* isEmergency */, null /* userHandle */); 403 } 404 buildPhoneAccountConfigureIntent( Context context, PhoneAccountHandle accountHandle)405 public static Intent buildPhoneAccountConfigureIntent( 406 Context context, PhoneAccountHandle accountHandle) { 407 Intent intent = buildConfigureIntent( 408 context, accountHandle, TelecomManager.ACTION_CONFIGURE_PHONE_ACCOUNT); 409 410 if (intent == null) { 411 // If the new configuration didn't work, try the old configuration intent. 412 intent = buildConfigureIntent( 413 context, accountHandle, LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT); 414 if (intent != null) { 415 Log.w(LOG_TAG, "Phone account using old configuration intent: " + accountHandle); 416 } 417 } 418 return intent; 419 } 420 buildConfigureIntent( Context context, PhoneAccountHandle accountHandle, String actionStr)421 private static Intent buildConfigureIntent( 422 Context context, PhoneAccountHandle accountHandle, String actionStr) { 423 if (accountHandle == null || accountHandle.getComponentName() == null || 424 TextUtils.isEmpty(accountHandle.getComponentName().getPackageName())) { 425 return null; 426 } 427 428 // Build the settings intent. 429 Intent intent = new Intent(actionStr); 430 intent.setPackage(accountHandle.getComponentName().getPackageName()); 431 intent.addCategory(Intent.CATEGORY_DEFAULT); 432 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); 433 434 // Check to see that the phone account package can handle the setting intent. 435 PackageManager pm = context.getPackageManager(); 436 List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0); 437 if (resolutions.size() == 0) { 438 intent = null; // set no intent if the package cannot handle it. 439 } 440 441 return intent; 442 } 443 444 /** 445 * @return Whether the current user is the primary user. 446 */ isPrimaryUser()447 private boolean isPrimaryUser() { 448 final UserManager userManager = (UserManager) getActivity() 449 .getSystemService(Context.USER_SERVICE); 450 return userManager.isPrimaryUser(); 451 } 452 updateMakeCallsOptions()453 private void updateMakeCallsOptions() { 454 if (mMakeAndReceiveCallsCategory == null) { 455 return; 456 } 457 458 Intent smartForwardingUiIntent = getLaunchSmartForwardingMenuIntent(); 459 if (smartForwardingUiIntent != null) { 460 mMakeAndReceiveCallsCategory.findPreference(SMART_FORWARDING_CONFIGURATION_PREF_KEY) 461 .setIntent(smartForwardingUiIntent); 462 mMakeAndReceiveCallsCategoryPresent = true; 463 } else { 464 mMakeAndReceiveCallsCategory.removePreference( 465 getPreferenceScreen().findPreference(SMART_FORWARDING_CONFIGURATION_PREF_KEY)); 466 } 467 468 if (!mMakeAndReceiveCallsCategoryPresent) { 469 getPreferenceScreen().removePreference(mMakeAndReceiveCallsCategory); 470 } 471 } 472 473 /** 474 * @return Smart forwarding configuration UI Intent when supported 475 */ getLaunchSmartForwardingMenuIntent()476 private Intent getLaunchSmartForwardingMenuIntent() { 477 if (mTelephonyManager.getPhoneCount() <= 1) { 478 return null; 479 } 480 481 final CarrierConfigManager configManager = (CarrierConfigManager) 482 getActivity().getSystemService(Context.CARRIER_CONFIG_SERVICE); 483 if (configManager == null) { 484 return null; 485 } 486 487 List<SubscriptionInfo> subscriptions = 488 mSubscriptionManager.getActiveSubscriptionInfoList(); 489 if (subscriptions == null) { 490 return null; 491 } 492 493 List<SubscriptionInfo> effectiveSubscriptions = subscriptions.stream() 494 .filter(subInfo -> !subInfo.isOpportunistic()) 495 .collect(Collectors.toList()); 496 if (effectiveSubscriptions.size() < 2) { 497 return null; 498 } 499 500 List<String> componentNames = effectiveSubscriptions.stream() 501 .map(subInfo -> configManager.getConfigForSubId(subInfo.getSubscriptionId())) 502 .filter(bundle -> (bundle != null)) 503 .map(bundle -> bundle.getString( 504 CarrierConfigManager.KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING)) 505 .filter(componentName -> !TextUtils.isEmpty(componentName)) 506 .collect(Collectors.toList()); 507 508 String componentNameOfMenu = null; 509 for (String componentName : componentNames) { 510 if (componentNameOfMenu == null) { 511 componentNameOfMenu = componentName; 512 } 513 else if (!componentNameOfMenu.equals(componentName)) { 514 Log.w(LOG_TAG, "ignore smart forward component: " + componentName); 515 } 516 } 517 518 if (TextUtils.isEmpty(componentNameOfMenu)) { 519 return null; 520 } 521 522 Intent intent = new Intent(Intent.ACTION_MAIN); 523 intent.setComponent(ComponentName.unflattenFromString(componentNameOfMenu)); 524 525 PackageManager pm = getActivity().getPackageManager(); 526 List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0); 527 if (resolutions.size() == 0) { 528 intent = null; // set no intent if no package can handle it. 529 } 530 531 return intent; 532 } 533 } 534