1 package com.android.phone.settings; 2 3 import android.content.Context; 4 import android.content.Intent; 5 import android.content.pm.PackageManager; 6 import android.content.pm.ResolveInfo; 7 import android.graphics.drawable.Icon; 8 import android.net.sip.SipManager; 9 import android.os.Bundle; 10 import android.preference.CheckBoxPreference; 11 import android.preference.ListPreference; 12 import android.preference.Preference; 13 import android.preference.PreferenceCategory; 14 import android.preference.PreferenceFragment; 15 import android.telecom.PhoneAccount; 16 import android.telecom.PhoneAccountHandle; 17 import android.telecom.TelecomManager; 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 import com.android.services.telephony.sip.SipAccountRegistry; 29 import com.android.services.telephony.sip.SipSharedPreferences; 30 import com.android.services.telephony.sip.SipUtil; 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 38 public class PhoneAccountSettingsFragment extends PreferenceFragment 39 implements Preference.OnPreferenceChangeListener, 40 AccountSelectionPreference.AccountSelectionListener { 41 42 private static final String ACCOUNTS_LIST_CATEGORY_KEY = 43 "phone_accounts_accounts_list_category_key"; 44 45 private static final String DEFAULT_OUTGOING_ACCOUNT_KEY = "default_outgoing_account"; 46 private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_account_all_calling_accounts"; 47 48 private static final String SIP_SETTINGS_CATEGORY_PREF_KEY = 49 "phone_accounts_sip_settings_category_key"; 50 private static final String USE_SIP_PREF_KEY = "use_sip_calling_options_key"; 51 private static final String SIP_RECEIVE_CALLS_PREF_KEY = "sip_receive_calls_key"; 52 53 private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT = 54 "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; 55 56 /** 57 * Value to start ordering of phone accounts relative to other preferences. By setting this 58 * value on the phone account listings, we ensure that anything that is ordered before 59 * {value} in the preference XML comes before the phone account list and anything with 60 * a value significantly larger will list after. 61 */ 62 private static final int ACCOUNT_ORDERING_START_VALUE = 100; 63 64 private static final String LOG_TAG = PhoneAccountSettingsFragment.class.getSimpleName(); 65 66 private TelecomManager mTelecomManager; 67 private TelephonyManager mTelephonyManager; 68 private SubscriptionManager mSubscriptionManager; 69 70 private PreferenceCategory mAccountList; 71 72 private AccountSelectionPreference mDefaultOutgoingAccount; 73 74 private ListPreference mUseSipCalling; 75 private CheckBoxPreference mSipReceiveCallsPreference; 76 private SipSharedPreferences mSipSharedPreferences; 77 78 @Override onCreate(Bundle icicle)79 public void onCreate(Bundle icicle) { 80 super.onCreate(icicle); 81 82 mTelecomManager = TelecomManager.from(getActivity()); 83 mTelephonyManager = TelephonyManager.from(getActivity()); 84 mSubscriptionManager = SubscriptionManager.from(getActivity()); 85 } 86 87 @Override onResume()88 public void onResume() { 89 super.onResume(); 90 91 if (getPreferenceScreen() != null) { 92 getPreferenceScreen().removeAll(); 93 } 94 95 addPreferencesFromResource(R.xml.phone_account_settings); 96 97 /** 98 * Here we make decisions about what we will and will not display with regards to phone- 99 * account settings. The basic settings structure is this: 100 * (1) <Make Calls With...> // Lets user pick a default account for outgoing calls 101 * (2) <Account List> 102 * <Account> 103 * ... 104 * <Account> 105 * </Account List> 106 * (3) <All Accounts> // Lets user enable/disable third-party accounts. SIM-based accounts 107 * // are always enabled and so aren't relevant here. 108 * 109 * Here are the rules that we follow: 110 * - (1) is only shown if there are multiple enabled accounts, including SIM accounts. 111 * This can be 2+ SIM accounts, 2+ third party accounts or any combination. 112 * - (2) The account list only lists (a) enabled third party accounts and (b) SIM-based 113 * accounts. However, for single-SIM devices, if the only account to show is the 114 * SIM-based account, we don't show the list at all under the assumption that the user 115 * already knows about the account. 116 * - (3) Is only shown if there exist any third party accounts. If none exist, then the 117 * option is hidden since there is nothing that can be done in it. 118 * 119 * By far, the most common case for users will be the single-SIM device without any 120 * third party accounts. IOW, the great majority of users won't see any of these options. 121 */ 122 mAccountList = (PreferenceCategory) getPreferenceScreen().findPreference( 123 ACCOUNTS_LIST_CATEGORY_KEY); 124 List<PhoneAccountHandle> allNonSimAccounts = 125 getCallingAccounts(false /* includeSims */, true /* includeDisabled */); 126 // Check to see if we should show the entire section at all. 127 if (shouldShowConnectionServiceList(allNonSimAccounts)) { 128 List<PhoneAccountHandle> enabledAccounts = 129 getCallingAccounts(true /* includeSims */, false /* includeDisabled */); 130 // Initialize the account list with the set of enabled & SIM accounts. 131 initAccountList(enabledAccounts); 132 133 mDefaultOutgoingAccount = (AccountSelectionPreference) 134 getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY); 135 mDefaultOutgoingAccount.setListener(this); 136 137 // Only show the 'Make Calls With..." option if there are multiple accounts. 138 if (enabledAccounts.size() > 1) { 139 updateDefaultOutgoingAccountsModel(); 140 } else { 141 mAccountList.removePreference(mDefaultOutgoingAccount); 142 } 143 144 Preference allAccounts = getPreferenceScreen().findPreference(ALL_CALLING_ACCOUNTS_KEY); 145 // If there are no third party (nonSim) accounts, then don't show enable/disable dialog. 146 if (allNonSimAccounts.isEmpty() && allAccounts != null) { 147 mAccountList.removePreference(allAccounts); 148 } 149 } else { 150 getPreferenceScreen().removePreference(mAccountList); 151 } 152 153 if (SipUtil.isVoipSupported(getActivity())) { 154 mSipSharedPreferences = new SipSharedPreferences(getActivity()); 155 156 mUseSipCalling = (ListPreference) 157 getPreferenceScreen().findPreference(USE_SIP_PREF_KEY); 158 mUseSipCalling.setEntries(!SipManager.isSipWifiOnly(getActivity()) 159 ? R.array.sip_call_options_wifi_only_entries 160 : R.array.sip_call_options_entries); 161 mUseSipCalling.setOnPreferenceChangeListener(this); 162 163 int optionsValueIndex = 164 mUseSipCalling.findIndexOfValue(mSipSharedPreferences.getSipCallOption()); 165 if (optionsValueIndex == -1) { 166 // If the option is invalid (eg. deprecated value), default to SIP_ADDRESS_ONLY. 167 mSipSharedPreferences.setSipCallOption( 168 getResources().getString(R.string.sip_address_only)); 169 optionsValueIndex = 170 mUseSipCalling.findIndexOfValue(mSipSharedPreferences.getSipCallOption()); 171 } 172 mUseSipCalling.setValueIndex(optionsValueIndex); 173 mUseSipCalling.setSummary(mUseSipCalling.getEntry()); 174 175 mSipReceiveCallsPreference = (CheckBoxPreference) 176 getPreferenceScreen().findPreference(SIP_RECEIVE_CALLS_PREF_KEY); 177 mSipReceiveCallsPreference.setEnabled(SipUtil.isPhoneIdle(getActivity())); 178 mSipReceiveCallsPreference.setChecked( 179 mSipSharedPreferences.isReceivingCallsEnabled()); 180 mSipReceiveCallsPreference.setOnPreferenceChangeListener(this); 181 } else { 182 getPreferenceScreen().removePreference( 183 getPreferenceScreen().findPreference(SIP_SETTINGS_CATEGORY_PREF_KEY)); 184 } 185 } 186 187 /** 188 * Handles changes to the preferences. 189 * 190 * @param pref The preference changed. 191 * @param objValue The changed value. 192 * @return True if the preference change has been handled, and false otherwise. 193 */ 194 @Override onPreferenceChange(Preference pref, Object objValue)195 public boolean onPreferenceChange(Preference pref, Object objValue) { 196 if (pref == mUseSipCalling) { 197 String option = objValue.toString(); 198 mSipSharedPreferences.setSipCallOption(option); 199 mUseSipCalling.setValueIndex(mUseSipCalling.findIndexOfValue(option)); 200 mUseSipCalling.setSummary(mUseSipCalling.getEntry()); 201 return true; 202 } else if (pref == mSipReceiveCallsPreference) { 203 final boolean isEnabled = !mSipReceiveCallsPreference.isChecked(); 204 new Thread(new Runnable() { 205 public void run() { 206 handleSipReceiveCallsOption(isEnabled); 207 } 208 }).start(); 209 return true; 210 } 211 return false; 212 } 213 214 /** 215 * Handles a phone account selection for the default outgoing phone account. 216 * 217 * @param pref The account selection preference which triggered the account selected event. 218 * @param account The account selected. 219 * @return True if the account selection has been handled, and false otherwise. 220 */ 221 @Override onAccountSelected(AccountSelectionPreference pref, PhoneAccountHandle account)222 public boolean onAccountSelected(AccountSelectionPreference pref, PhoneAccountHandle account) { 223 if (pref == mDefaultOutgoingAccount) { 224 mTelecomManager.setUserSelectedOutgoingPhoneAccount(account); 225 return true; 226 } 227 return false; 228 } 229 230 /** 231 * Repopulate the dialog to pick up changes before showing. 232 * 233 * @param pref The account selection preference dialog being shown. 234 */ 235 @Override onAccountSelectionDialogShow(AccountSelectionPreference pref)236 public void onAccountSelectionDialogShow(AccountSelectionPreference pref) { 237 if (pref == mDefaultOutgoingAccount) { 238 updateDefaultOutgoingAccountsModel(); 239 } 240 } 241 242 @Override onAccountChanged(AccountSelectionPreference pref)243 public void onAccountChanged(AccountSelectionPreference pref) {} 244 handleSipReceiveCallsOption(boolean isEnabled)245 private synchronized void handleSipReceiveCallsOption(boolean isEnabled) { 246 Context context = getActivity(); 247 if (context == null) { 248 // Return if the fragment is detached from parent activity before executed by thread. 249 return; 250 } 251 252 mSipSharedPreferences.setReceivingCallsEnabled(isEnabled); 253 254 SipUtil.useSipToReceiveIncomingCalls(context, isEnabled); 255 256 // Restart all Sip services to ensure we reflect whether we are receiving calls. 257 SipAccountRegistry sipAccountRegistry = SipAccountRegistry.getInstance(); 258 sipAccountRegistry.restartSipService(context); 259 } 260 261 /** 262 * Queries the telcomm manager to update the default outgoing account selection preference 263 * with the list of outgoing accounts and the current default outgoing account. 264 */ updateDefaultOutgoingAccountsModel()265 private void updateDefaultOutgoingAccountsModel() { 266 mDefaultOutgoingAccount.setModel( 267 mTelecomManager, 268 getCallingAccounts(true /* includeSims */, false /* includeDisabled */), 269 mTelecomManager.getUserSelectedOutgoingPhoneAccount(), 270 getString(R.string.phone_accounts_ask_every_time)); 271 } 272 initAccountList(List<PhoneAccountHandle> enabledAccounts)273 private void initAccountList(List<PhoneAccountHandle> enabledAccounts) { 274 275 boolean isMultiSimDevice = mTelephonyManager.isMultiSimEnabled(); 276 277 // On a single-SIM device, do not list any accounts if the only account is the SIM-based 278 // one. This is because on single-SIM devices, we do not expose SIM settings through the 279 // account listing entry so showing it does nothing to help the user. Nor does the lack of 280 // action match the "Settings" header above the listing. 281 if (!isMultiSimDevice && getCallingAccounts( 282 false /* includeSims */, false /* includeDisabled */).isEmpty()){ 283 return; 284 } 285 286 // Obtain the list of phone accounts. 287 List<PhoneAccount> accounts = new ArrayList<>(); 288 for (PhoneAccountHandle handle : enabledAccounts) { 289 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 290 if (account != null) { 291 accounts.add(account); 292 } 293 } 294 295 // Sort the accounts according to how we want to display them. 296 Collections.sort(accounts, new Comparator<PhoneAccount>() { 297 @Override 298 public int compare(PhoneAccount account1, PhoneAccount account2) { 299 int retval = 0; 300 301 // SIM accounts go first 302 boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 303 boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 304 if (isSim1 != isSim2) { 305 retval = isSim1 ? -1 : 1; 306 } 307 308 // Then order by package 309 if (retval == 0) { 310 String pkg1 = account1.getAccountHandle().getComponentName().getPackageName(); 311 String pkg2 = account2.getAccountHandle().getComponentName().getPackageName(); 312 retval = pkg1.compareTo(pkg2); 313 } 314 315 // Finally, order by label 316 if (retval == 0) { 317 String label1 = nullToEmpty(account1.getLabel().toString()); 318 String label2 = nullToEmpty(account2.getLabel().toString()); 319 retval = label1.compareTo(label2); 320 } 321 322 // Then by hashcode 323 if (retval == 0) { 324 retval = account1.hashCode() - account2.hashCode(); 325 } 326 return retval; 327 } 328 }); 329 330 int order = ACCOUNT_ORDERING_START_VALUE; 331 332 // Add an entry for each account. 333 for (PhoneAccount account : accounts) { 334 PhoneAccountHandle handle = account.getAccountHandle(); 335 Intent intent = null; 336 337 // SIM phone accounts use a different setting intent and are thus handled differently. 338 if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 339 340 // For SIM-based accounts, we only expose the settings through the account list 341 // if we are on a multi-SIM device. For single-SIM devices, the settings are 342 // more spread out so there is no good single place to take the user, so we don't. 343 if (isMultiSimDevice) { 344 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo( 345 mTelephonyManager.getSubIdForPhoneAccount(account)); 346 347 if (subInfo != null) { 348 intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS); 349 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 350 SubscriptionInfoHelper.addExtrasToIntent(intent, subInfo); 351 } 352 } 353 } else { 354 intent = buildPhoneAccountConfigureIntent(getActivity(), handle); 355 } 356 357 // Create the preference & add the label 358 Preference accountPreference = new Preference(getActivity()); 359 accountPreference.setTitle(account.getLabel()); 360 361 // Add an icon. 362 Icon icon = account.getIcon(); 363 if (icon != null) { 364 accountPreference.setIcon(icon.loadDrawable(getActivity())); 365 } 366 367 // Add an intent to send the user to the account's settings. 368 if (intent != null) { 369 accountPreference.setIntent(intent); 370 } 371 372 accountPreference.setOrder(order++); 373 mAccountList.addPreference(accountPreference); 374 } 375 } 376 shouldShowConnectionServiceList(List<PhoneAccountHandle> allNonSimAccounts)377 private boolean shouldShowConnectionServiceList(List<PhoneAccountHandle> allNonSimAccounts) { 378 return mTelephonyManager.isMultiSimEnabled() || allNonSimAccounts.size() > 0; 379 } 380 getCallingAccounts( boolean includeSims, boolean includeDisabledAccounts)381 private List<PhoneAccountHandle> getCallingAccounts( 382 boolean includeSims, boolean includeDisabledAccounts) { 383 PhoneAccountHandle emergencyAccountHandle = getEmergencyPhoneAccount(); 384 385 List<PhoneAccountHandle> accountHandles = 386 mTelecomManager.getCallCapablePhoneAccounts(includeDisabledAccounts); 387 for (Iterator<PhoneAccountHandle> i = accountHandles.iterator(); i.hasNext();) { 388 PhoneAccountHandle handle = i.next(); 389 if (handle.equals(emergencyAccountHandle)) { 390 // never include emergency call accounts in this piece of code. 391 i.remove(); 392 continue; 393 } 394 395 PhoneAccount account = mTelecomManager.getPhoneAccount(handle); 396 if (account == null) { 397 i.remove(); 398 } else if (!includeSims && 399 account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 400 i.remove(); 401 } 402 } 403 return accountHandles; 404 } 405 nullToEmpty(String str)406 private String nullToEmpty(String str) { 407 return str == null ? "" : str; 408 } 409 getEmergencyPhoneAccount()410 private PhoneAccountHandle getEmergencyPhoneAccount() { 411 return PhoneUtils.makePstnPhoneAccountHandleWithPrefix( 412 (Phone) null, "" /* prefix */, true /* isEmergency */); 413 } 414 buildPhoneAccountConfigureIntent( Context context, PhoneAccountHandle accountHandle)415 public static Intent buildPhoneAccountConfigureIntent( 416 Context context, PhoneAccountHandle accountHandle) { 417 Intent intent = buildConfigureIntent( 418 context, accountHandle, TelecomManager.ACTION_CONFIGURE_PHONE_ACCOUNT); 419 420 if (intent == null) { 421 // If the new configuration didn't work, try the old configuration intent. 422 intent = buildConfigureIntent( 423 context, accountHandle, LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT); 424 if (intent != null) { 425 Log.w(LOG_TAG, "Phone account using old configuration intent: " + accountHandle); 426 } 427 } 428 return intent; 429 } 430 buildConfigureIntent( Context context, PhoneAccountHandle accountHandle, String actionStr)431 private static Intent buildConfigureIntent( 432 Context context, PhoneAccountHandle accountHandle, String actionStr) { 433 if (accountHandle == null || accountHandle.getComponentName() == null || 434 TextUtils.isEmpty(accountHandle.getComponentName().getPackageName())) { 435 return null; 436 } 437 438 // Build the settings intent. 439 Intent intent = new Intent(actionStr); 440 intent.setPackage(accountHandle.getComponentName().getPackageName()); 441 intent.addCategory(Intent.CATEGORY_DEFAULT); 442 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); 443 444 // Check to see that the phone account package can handle the setting intent. 445 PackageManager pm = context.getPackageManager(); 446 List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0); 447 if (resolutions.size() == 0) { 448 intent = null; // set no intent if the package cannot handle it. 449 } 450 451 return intent; 452 } 453 } 454