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