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