• 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             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