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