• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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