1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.accounts;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_CLONE;
20 import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
21 import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
22 import static android.app.admin.DevicePolicyResources.Strings.Settings.CLONE_CATEGORY_HEADER;
23 import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
24 import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
25 import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
26 import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
27 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
28 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
29 import static android.content.Intent.EXTRA_USER;
30 import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
31 import static android.os.UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
32 import static android.provider.Settings.ACTION_ADD_ACCOUNT;
33 import static android.provider.Settings.EXTRA_AUTHORITIES;
34 
35 import android.accounts.Account;
36 import android.accounts.AccountManager;
37 import android.app.admin.DevicePolicyManager;
38 import android.content.BroadcastReceiver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.pm.ApplicationInfo;
43 import android.content.pm.PackageManager;
44 import android.content.pm.UserInfo;
45 import android.content.pm.UserProperties;
46 import android.content.res.Resources;
47 import android.graphics.drawable.Drawable;
48 import android.os.Bundle;
49 import android.os.Flags;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.text.BidiFormatter;
53 import android.util.ArrayMap;
54 import android.util.Log;
55 import android.util.SparseArray;
56 
57 import androidx.annotation.VisibleForTesting;
58 import androidx.preference.Preference;
59 import androidx.preference.Preference.OnPreferenceClickListener;
60 import androidx.preference.PreferenceGroup;
61 import androidx.preference.PreferenceScreen;
62 
63 import com.android.settings.AccessiblePreferenceCategory;
64 import com.android.settings.R;
65 import com.android.settings.Utils;
66 import com.android.settings.core.PreferenceControllerMixin;
67 import com.android.settings.core.SubSettingLauncher;
68 import com.android.settings.dashboard.DashboardFragment;
69 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
70 import com.android.settings.overlay.FeatureFactory;
71 import com.android.settingslib.RestrictedPreference;
72 import com.android.settingslib.accounts.AuthenticatorHelper;
73 import com.android.settingslib.core.AbstractPreferenceController;
74 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
75 import com.android.settingslib.core.lifecycle.LifecycleObserver;
76 import com.android.settingslib.core.lifecycle.events.OnPause;
77 import com.android.settingslib.core.lifecycle.events.OnResume;
78 import com.android.settingslib.search.SearchIndexableRaw;
79 
80 import java.util.ArrayList;
81 import java.util.Collections;
82 import java.util.Comparator;
83 import java.util.List;
84 
85 public class AccountPreferenceController extends AbstractPreferenceController
86         implements PreferenceControllerMixin, AuthenticatorHelper.OnAccountsUpdateListener,
87         OnPreferenceClickListener, LifecycleObserver, OnPause, OnResume {
88 
89     private static final String TAG = "AccountPrefController";
90 
91     private static final int ORDER_ACCOUNT_PROFILES = 101;
92     private static final int ORDER_LAST = 1002;
93     private static final int ORDER_NEXT_TO_LAST = 1001;
94     private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
95 
96     private static final String PREF_KEY_ADD_ACCOUNT = "add_account";
97     private static final String PREF_KEY_REMOVE_PROFILE = "remove_profile";
98     private static final String PREF_KEY_WORK_PROFILE_SETTING = "work_profile_setting";
99 
100     private UserManager mUm;
101     private DevicePolicyManager mDpm;
102     private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
103     private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver =
104             new ManagedProfileBroadcastReceiver();
105     private String[] mAuthorities;
106     private int mAuthoritiesCount = 0;
107     private DashboardFragment mFragment;
108     private int mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
109     private AccountRestrictionHelper mHelper;
110     private MetricsFeatureProvider mMetricsFeatureProvider;
111     private @ProfileSelectFragment.ProfileType int mType;
112 
113     /**
114      * Holds data related to the accounts belonging to one profile.
115      */
116     public static class ProfileData {
117         /**
118          * The preference that displays the accounts.
119          */
120         public PreferenceGroup preferenceGroup;
121         /**
122          * The preference that displays the add account button.
123          */
124         public RestrictedPreference addAccountPreference;
125         /**
126          * The preference that displays the button to remove the managed profile
127          */
128         public RestrictedPreference removeWorkProfilePreference;
129         /**
130          * The preference that displays managed profile settings.
131          */
132         public Preference managedProfilePreference;
133         /**
134          * The {@link AuthenticatorHelper} that holds accounts data for this profile.
135          */
136         public AuthenticatorHelper authenticatorHelper;
137         /**
138          * The {@link UserInfo} of the profile.
139          */
140         public UserInfo userInfo;
141         /**
142          * The {@link UserInfo} of the profile.
143          */
144         public boolean pendingRemoval;
145         /**
146          * The map from account key to account preference
147          */
148         public ArrayMap<String, AccountTypePreference> accountPreferences = new ArrayMap<>();
149     }
150 
AccountPreferenceController(Context context, DashboardFragment parent, String[] authorities, @ProfileSelectFragment.ProfileType int type)151     public AccountPreferenceController(Context context, DashboardFragment parent,
152             String[] authorities, @ProfileSelectFragment.ProfileType int type) {
153         this(context, parent, authorities, new AccountRestrictionHelper(context), type);
154     }
155 
156     @VisibleForTesting
AccountPreferenceController(Context context, DashboardFragment parent, String[] authorities, AccountRestrictionHelper helper, @ProfileSelectFragment.ProfileType int type)157     AccountPreferenceController(Context context, DashboardFragment parent,
158             String[] authorities, AccountRestrictionHelper helper,
159             @ProfileSelectFragment.ProfileType int type) {
160         super(context);
161         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
162         mDpm = context.getSystemService(DevicePolicyManager.class);
163         mAuthorities = authorities;
164         mFragment = parent;
165         if (mAuthorities != null) {
166             mAuthoritiesCount = mAuthorities.length;
167         }
168         final FeatureFactory featureFactory = FeatureFactory.getFeatureFactory();
169         mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
170         mHelper = helper;
171         mType = type;
172     }
173 
174     @Override
isAvailable()175     public boolean isAvailable() {
176         return !mUm.isManagedProfile();
177     }
178 
179     @Override
getPreferenceKey()180     public String getPreferenceKey() {
181         return null;
182     }
183 
184     @Override
displayPreference(PreferenceScreen screen)185     public void displayPreference(PreferenceScreen screen) {
186         super.displayPreference(screen);
187         updateUi();
188     }
189 
190     @Override
updateRawDataToIndex(List<SearchIndexableRaw> rawData)191     public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
192         rawData.add(newAddAccountRawData());
193     }
194 
195     @Override
updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData)196     public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
197         if (!isAvailable()) {
198             return;
199         }
200         final Resources res = mContext.getResources();
201         final String screenTitle = res.getString(R.string.account_settings_title);
202 
203         List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
204         for (final UserInfo userInfo : profiles) {
205             if (userInfo.isEnabled() && userInfo.isManagedProfile()) {
206                 if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_MANAGED_PROFILE,
207                         UserHandle.myUserId())) {
208                     final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
209                     data.key = PREF_KEY_REMOVE_PROFILE;
210                     data.title = mDpm.getResources().getString(
211                             REMOVE_WORK_PROFILE,
212                             () -> res.getString(R.string.remove_managed_profile_label));
213                     data.screenTitle = screenTitle;
214                     rawData.add(data);
215                 }
216                 final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
217                 data.key = PREF_KEY_WORK_PROFILE_SETTING;
218                 data.title = mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
219                         () -> res.getString(R.string.managed_profile_settings_title));
220                 data.screenTitle = screenTitle;
221                 rawData.add(data);
222             }
223         }
224     }
225 
226     @Override
onResume()227     public void onResume() {
228         updateUi();
229         mManagedProfileBroadcastReceiver.register(mContext);
230         listenToAccountUpdates();
231     }
232 
233     @Override
onPause()234     public void onPause() {
235         stopListeningToAccountUpdates();
236         mManagedProfileBroadcastReceiver.unregister(mContext);
237     }
238 
239     @Override
onAccountsUpdate(UserHandle userHandle)240     public void onAccountsUpdate(UserHandle userHandle) {
241         final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
242         if (profileData != null) {
243             updateAccountTypes(profileData);
244         } else {
245             Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
246         }
247     }
248 
249     @Override
onPreferenceClick(Preference preference)250     public boolean onPreferenceClick(Preference preference) {
251         final int metricsCategory = mFragment.getMetricsCategory();
252         // Check the preference
253         final int count = mProfiles.size();
254         for (int i = 0; i < count; i++) {
255             ProfileData profileData = mProfiles.valueAt(i);
256             if (preference == profileData.addAccountPreference) {
257                 mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
258                 Intent intent = new Intent(ACTION_ADD_ACCOUNT);
259                 intent.setClass(mContext, AddAccountSettings.class);
260                 intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
261                 intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
262                 mContext.startActivity(intent);
263                 return true;
264             }
265             if (preference == profileData.removeWorkProfilePreference) {
266                 mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
267                 final int userId = profileData.userInfo.id;
268                 RemoveUserFragment.newInstance(userId).show(mFragment.getFragmentManager(),
269                         "removeUser");
270                 return true;
271             }
272             if (preference == profileData.managedProfilePreference) {
273                 mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
274                 Bundle arguments = new Bundle();
275                 arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
276                 new SubSettingLauncher(mContext)
277                         .setSourceMetricsCategory(metricsCategory)
278                         .setDestination(ManagedProfileSettings.class.getName())
279                         .setTitleText(mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
280                                 () -> mContext.getString(R.string.managed_profile_settings_title)))
281                         .setArguments(arguments)
282                         .launch();
283 
284                 return true;
285             }
286         }
287         return false;
288     }
289 
updateUi()290     private void updateUi() {
291         if (!isAvailable()) {
292             // This should not happen
293             Log.e(TAG, "We should not be showing settings for a managed profile");
294             return;
295         }
296 
297         for (int i = 0, size = mProfiles.size(); i < size; i++) {
298             mProfiles.valueAt(i).pendingRemoval = true;
299         }
300         if (mUm.isRestrictedProfile()) {
301             // Restricted user or similar
302             UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
303             updateProfileUi(userInfo);
304         } else {
305             List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
306             for (UserInfo profile : profiles) {
307                 // Check if this controller can handle this profile - e.g. if this controller's
308                 // mType has the WORK flag set and this profile is a managed profile.
309                 // If there are no tabs then this controller will support all profile types -
310                 // - ProfileType.ALL.
311                 // At the same time we should check the user property to make sure if this profile
312                 // should be shown or not.
313                 if (((profile.isManagedProfile()
314                         && (mType & ProfileSelectFragment.ProfileType.WORK) != 0)
315                         || (isPrivateProfile(profile)
316                             && (mType & ProfileSelectFragment.ProfileType.PRIVATE) != 0)
317                         || (!profile.isManagedProfile()
318                             && !isPrivateProfile(profile)
319                             && (mType & ProfileSelectFragment.ProfileType.PERSONAL) != 0))
320                         && !(mUm.getUserProperties(profile.getUserHandle())
321                             .getShowInQuietMode() == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN
322                             && profile.isQuietModeEnabled())) {
323                     updateProfileUi(profile);
324                 }
325             }
326         }
327         cleanUpPreferences();
328 
329         // Add all preferences, starting with one for the primary profile.
330         // Note that we're relying on the ordering given by the SparseArray keys, and on the
331         // value of UserHandle.USER_OWNER being smaller than all the rest.
332         final int profilesCount = mProfiles.size();
333         for (int i = 0; i < profilesCount; i++) {
334             updateAccountTypes(mProfiles.valueAt(i));
335         }
336 
337         // Refresh for the auto-sync preferences
338         mFragment.forceUpdatePreferences();
339     }
340 
isPrivateProfile(UserInfo profile)341     private static boolean isPrivateProfile(UserInfo profile) {
342         return Flags.allowPrivateProfile()
343                 && android.multiuser.Flags.enablePrivateSpaceFeatures()
344                 && profile.isPrivateProfile();
345     }
346 
updateProfileUi(final UserInfo userInfo)347     private void updateProfileUi(final UserInfo userInfo) {
348         if (mFragment.getPreferenceManager() == null) {
349             return;
350         }
351         final ProfileData data = mProfiles.get(userInfo.id);
352         if (data != null) {
353             data.pendingRemoval = false;
354             data.userInfo = userInfo;
355             if (userInfo.isEnabled()) {
356                 // recreate the authentication helper to refresh the list of enabled accounts
357                 data.authenticatorHelper =
358                         new AuthenticatorHelper(mContext, userInfo.getUserHandle(), this);
359             }
360             return;
361         }
362         if (mUm.getUserProperties(userInfo.getUserHandle()).getShowInSettings()
363                 == UserProperties.SHOW_IN_SETTINGS_NO) {
364             return;
365         }
366         final Context context = mContext;
367         final ProfileData profileData = new ProfileData();
368         profileData.userInfo = userInfo;
369         AccessiblePreferenceCategory preferenceGroup =
370                 mHelper.createAccessiblePreferenceCategory(
371                         mFragment.getPreferenceManager().getContext());
372         preferenceGroup.setOrder(mAccountProfileOrder++);
373         preferenceGroup.setTitle(R.string.account_settings); // default title; may be modified below
374         if (isSingleProfile()) {
375             final String title = context.getString(R.string.account_for_section_header,
376                     BidiFormatter.getInstance().unicodeWrap(userInfo.name));
377             preferenceGroup.setTitle(title);
378             preferenceGroup.setContentDescription(title);
379         } else if (userInfo.isManagedProfile()) {
380             if (mType == ProfileSelectFragment.ProfileType.ALL) {
381                 setCategoryTitleFromDevicePolicyResource(preferenceGroup, WORK_CATEGORY_HEADER,
382                         com.android.settingslib.R.string.category_work);
383                 final String workGroupSummary = getWorkGroupSummary(context, userInfo);
384                 preferenceGroup.setSummary(workGroupSummary);
385                 setContentDescriptionFromDevicePolicyResource(preferenceGroup,
386                         ACCESSIBILITY_CATEGORY_WORK, R.string.accessibility_category_work,
387                         workGroupSummary);
388             }
389             profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference();
390             mHelper.enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
391                     DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.myUserId());
392             profileData.managedProfilePreference = newManagedProfileSettings();
393         } else if (userInfo.isCloneProfile()) {
394             if (mType == ProfileSelectFragment.ProfileType.ALL) {
395                 setCategoryTitleFromDevicePolicyResource(preferenceGroup, CLONE_CATEGORY_HEADER,
396                         com.android.settingslib.R.string.category_clone);
397                 setContentDescriptionFromDevicePolicyResource(preferenceGroup,
398                         ACCESSIBILITY_CATEGORY_CLONE, R.string.accessibility_category_clone,
399                         null);
400             }
401         } else {
402             // Primary Profile
403             if (mType == ProfileSelectFragment.ProfileType.ALL) {
404                 setCategoryTitleFromDevicePolicyResource(preferenceGroup, PERSONAL_CATEGORY_HEADER,
405                         com.android.settingslib.R.string.category_personal);
406                 setContentDescriptionFromDevicePolicyResource(preferenceGroup,
407                         ACCESSIBILITY_CATEGORY_PERSONAL, R.string.accessibility_category_personal,
408                         null);
409             }
410         }
411         final PreferenceScreen screen = mFragment.getPreferenceScreen();
412         if (screen != null) {
413             screen.addPreference(preferenceGroup);
414         }
415         profileData.preferenceGroup = preferenceGroup;
416         if (userInfo.isEnabled()) {
417             profileData.authenticatorHelper = new AuthenticatorHelper(context,
418                     userInfo.getUserHandle(), this);
419             if (!userInfo.isCloneProfile()) {
420                 profileData.addAccountPreference = newAddAccountPreference();
421                 mHelper.enforceRestrictionOnPreference(profileData.addAccountPreference,
422                         DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
423             }
424         }
425         mProfiles.put(userInfo.id, profileData);
426     }
427 
setCategoryTitleFromDevicePolicyResource( AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier)428     private void setCategoryTitleFromDevicePolicyResource(
429             AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier) {
430         preferenceGroup.setTitle(
431                 mDpm.getResources().getString(stringId,
432                         () -> mContext.getString(resourceIdentifier)));
433     }
434 
setContentDescriptionFromDevicePolicyResource( AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier, String formatArgs)435     private void setContentDescriptionFromDevicePolicyResource(
436             AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier,
437             String formatArgs) {
438         preferenceGroup.setContentDescription(mDpm.getResources().getString(stringId, () -> {
439             if (formatArgs != null) {
440                 return mContext.getString(resourceIdentifier, formatArgs);
441             }
442             return mContext.getString(resourceIdentifier);
443         }));
444     }
445 
newAddAccountRawData()446     private SearchIndexableRaw newAddAccountRawData() {
447         SearchIndexableRaw data = new SearchIndexableRaw(mContext);
448         data.key = PREF_KEY_ADD_ACCOUNT;
449         data.title = mContext.getString(R.string.add_account_label);
450         data.iconResId = R.drawable.ic_add_24dp;
451         return data;
452     }
453 
newAddAccountPreference()454     private RestrictedPreference newAddAccountPreference() {
455         RestrictedPreference preference =
456                 new RestrictedPreference(mFragment.getPreferenceManager().getContext());
457         preference.setKey(PREF_KEY_ADD_ACCOUNT);
458         preference.setTitle(R.string.add_account_label);
459         preference.setIcon(R.drawable.ic_add_24dp);
460         preference.setOnPreferenceClickListener(this);
461         preference.setOrder(ORDER_NEXT_TO_NEXT_TO_LAST);
462         return preference;
463     }
464 
newRemoveWorkProfilePreference()465     private RestrictedPreference newRemoveWorkProfilePreference() {
466         RestrictedPreference preference = new RestrictedPreference(
467                 mFragment.getPreferenceManager().getContext());
468         preference.setKey(PREF_KEY_REMOVE_PROFILE);
469         preference.setTitle(
470                 mDpm.getResources().getString(REMOVE_WORK_PROFILE,
471                         () -> mContext.getString(R.string.remove_managed_profile_label)));
472         preference.setIcon(R.drawable.ic_delete);
473         preference.setOnPreferenceClickListener(this);
474         preference.setOrder(ORDER_LAST);
475         return preference;
476     }
477 
newManagedProfileSettings()478     private Preference newManagedProfileSettings() {
479         Preference preference = new Preference(mFragment.getPreferenceManager().getContext());
480         preference.setKey(PREF_KEY_WORK_PROFILE_SETTING);
481         preference.setTitle(mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
482                 () -> mContext.getString(R.string.managed_profile_settings_title)));
483         preference.setIcon(R.drawable.ic_settings_24dp);
484         preference.setOnPreferenceClickListener(this);
485         preference.setOrder(ORDER_NEXT_TO_LAST);
486         return preference;
487     }
488 
getWorkGroupSummary(Context context, UserInfo userInfo)489     private String getWorkGroupSummary(Context context, UserInfo userInfo) {
490         PackageManager packageManager = context.getPackageManager();
491         ApplicationInfo adminApplicationInfo = Utils.getAdminApplicationInfo(context, userInfo.id);
492         if (adminApplicationInfo == null) {
493             return null;
494         }
495         CharSequence appLabel = packageManager.getApplicationLabel(adminApplicationInfo);
496         return mDpm.getResources().getString(MANAGED_BY,
497                 () -> mContext.getString(R.string.managing_admin, appLabel), appLabel);
498     }
499 
cleanUpPreferences()500     void cleanUpPreferences() {
501         PreferenceScreen screen = mFragment.getPreferenceScreen();
502         if (screen == null) {
503             return;
504         }
505         final int count = mProfiles.size();
506         for (int i = count - 1; i >= 0; i--) {
507             final ProfileData data = mProfiles.valueAt(i);
508             if (data.pendingRemoval) {
509                 screen.removePreference(data.preferenceGroup);
510                 mProfiles.removeAt(i);
511             }
512         }
513     }
514 
listenToAccountUpdates()515     private void listenToAccountUpdates() {
516         final int count = mProfiles.size();
517         for (int i = 0; i < count; i++) {
518             AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
519             if (authenticatorHelper != null) {
520                 authenticatorHelper.listenToAccountUpdates();
521             }
522         }
523     }
524 
stopListeningToAccountUpdates()525     private void stopListeningToAccountUpdates() {
526         final int count = mProfiles.size();
527         for (int i = 0; i < count; i++) {
528             AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
529             if (authenticatorHelper != null) {
530                 authenticatorHelper.stopListeningToAccountUpdates();
531             }
532         }
533     }
534 
updateAccountTypes(ProfileData profileData)535     private void updateAccountTypes(ProfileData profileData) {
536         if (mFragment.getPreferenceManager() == null
537                 || profileData.preferenceGroup.getPreferenceManager() == null) {
538             // This could happen if activity is finishing
539             return;
540         }
541         if (profileData.userInfo.isEnabled()) {
542             final ArrayMap<String, AccountTypePreference> preferenceToRemove =
543                     new ArrayMap<>(profileData.accountPreferences);
544             final ArrayList<AccountTypePreference> preferences = getAccountTypePreferences(
545                     profileData.authenticatorHelper, profileData.userInfo.getUserHandle(),
546                     preferenceToRemove);
547             final int count = preferences.size();
548             for (int i = 0; i < count; i++) {
549                 final AccountTypePreference preference = preferences.get(i);
550                 preference.setOrder(i);
551                 final String key = preference.getKey();
552                 if (!profileData.accountPreferences.containsKey(key)) {
553                     profileData.preferenceGroup.addPreference(preference);
554                     profileData.accountPreferences.put(key, preference);
555                 }
556             }
557             if (profileData.addAccountPreference != null) {
558                 profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
559             }
560             for (String key : preferenceToRemove.keySet()) {
561                 profileData.preferenceGroup.removePreference(
562                         profileData.accountPreferences.get(key));
563                 profileData.accountPreferences.remove(key);
564             }
565         } else {
566             profileData.preferenceGroup.removeAll();
567             // Put a label instead of the accounts list
568             final Preference profileNotAvailablePreference =
569                     new Preference(mFragment.getPreferenceManager().getContext());
570             profileNotAvailablePreference.setEnabled(false);
571             profileNotAvailablePreference.setIcon(R.drawable.empty_icon);
572             profileNotAvailablePreference.setTitle(null);
573             profileNotAvailablePreference.setSummary(
574                     mDpm.getResources()
575                             .getString(
576                                     WORK_PROFILE_NOT_AVAILABLE,
577                                     () ->
578                                             mContext.getString(
579                                                     R.string.managed_profile_not_available_label)));
580             profileData.preferenceGroup.addPreference(profileNotAvailablePreference);
581         }
582         if (profileData.removeWorkProfilePreference != null) {
583             profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
584         }
585         if (profileData.managedProfilePreference != null) {
586             profileData.preferenceGroup.addPreference(profileData.managedProfilePreference);
587         }
588     }
589 
getAccountTypePreferences(AuthenticatorHelper helper, UserHandle userHandle, ArrayMap<String, AccountTypePreference> preferenceToRemove)590     private ArrayList<AccountTypePreference> getAccountTypePreferences(AuthenticatorHelper helper,
591             UserHandle userHandle, ArrayMap<String, AccountTypePreference> preferenceToRemove) {
592         final String[] accountTypes = helper.getEnabledAccountTypes();
593         final ArrayList<AccountTypePreference> accountTypePreferences =
594                 new ArrayList<>(accountTypes.length);
595 
596         for (int i = 0; i < accountTypes.length; i++) {
597             final String accountType = accountTypes[i];
598             // Skip showing any account that does not have any of the requested authorities
599             if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
600                 continue;
601             }
602             final CharSequence label = helper.getLabelForType(mContext, accountType);
603             if (label == null) {
604                 continue;
605             }
606             final String titleResPackageName = helper.getPackageForType(accountType);
607             final int titleResId = helper.getLabelIdForType(accountType);
608 
609             final Account[] accounts = AccountManager.get(mContext)
610                     .getAccountsByTypeAsUser(accountType, userHandle);
611             final Drawable icon = helper.getDrawableForType(mContext, accountType);
612             final Context prefContext = mFragment.getPreferenceManager().getContext();
613 
614             // Add a preference row for each individual account
615             for (Account account : accounts) {
616                 final AccountTypePreference preference =
617                         preferenceToRemove.remove(AccountTypePreference.buildKey(account));
618                 if (preference != null) {
619                     accountTypePreferences.add(preference);
620                     continue;
621                 }
622                 final ArrayList<String> auths =
623                         helper.getAuthoritiesForAccountType(account.type);
624                 if (!AccountRestrictionHelper.showAccount(mAuthorities, auths)) {
625                     continue;
626                 }
627                 final Bundle fragmentArguments = new Bundle();
628                 fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT,
629                         account);
630                 fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE,
631                         userHandle);
632                 fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE,
633                         accountType);
634                 fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL,
635                         label.toString());
636                 fragmentArguments.putInt(AccountDetailDashboardFragment.KEY_ACCOUNT_TITLE_RES,
637                         titleResId);
638                 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
639                 accountTypePreferences.add(new AccountTypePreference(
640                         prefContext, mMetricsFeatureProvider.getMetricsCategory(mFragment),
641                         account, titleResPackageName, titleResId, label,
642                         AccountDetailDashboardFragment.class.getName(), fragmentArguments, icon));
643             }
644             helper.preloadDrawableForType(mContext, accountType);
645         }
646         // Sort by label
647         Collections.sort(accountTypePreferences, new Comparator<AccountTypePreference>() {
648             @Override
649             public int compare(AccountTypePreference t1, AccountTypePreference t2) {
650                 int result = t1.getSummary().toString().compareTo(t2.getSummary().toString());
651                 return result != 0
652                         ? result : t1.getTitle().toString().compareTo(t2.getTitle().toString());
653             }
654         });
655         return accountTypePreferences;
656     }
657 
accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper, String accountType)658     private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
659             String accountType) {
660         if (mAuthoritiesCount == 0) {
661             // No authorities required
662             return true;
663         }
664         final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
665                 accountType);
666         if (authoritiesForType == null) {
667             Log.d(TAG, "No sync authorities for account type: " + accountType);
668             return false;
669         }
670         for (int j = 0; j < mAuthoritiesCount; j++) {
671             if (authoritiesForType.contains(mAuthorities[j])) {
672                 return true;
673             }
674         }
675         return false;
676     }
677 
isSingleProfile()678     private boolean isSingleProfile() {
679         return mUm.isLinkedUser() || mUm.getProfiles(UserHandle.myUserId()).size() == 1;
680     }
681 
682     private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
683         private boolean mListeningToManagedProfileEvents;
684 
685         @Override
onReceive(Context context, Intent intent)686         public void onReceive(Context context, Intent intent) {
687             final String action = intent.getAction();
688             Log.v(TAG, "Received broadcast: " + action);
689             if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
690                     || action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
691                 if (mFragment instanceof AccountWorkProfileDashboardFragment) {
692                     new SubSettingLauncher(context)
693                             .setDestination(AccountDashboardFragment.class.getName())
694                             .setSourceMetricsCategory(mFragment.getMetricsCategory())
695                             .setTitleRes(-1)
696                             .setIsSecondLayerPage(true)
697                             .launch();
698                     mFragment.getActivity().finish();
699                 } else {
700                     // Clean old state
701                     stopListeningToAccountUpdates();
702                     // Build new state
703                     updateUi();
704                     listenToAccountUpdates();
705                 }
706                 return;
707             }
708             Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
709         }
710 
register(Context context)711         public void register(Context context) {
712             if (!mListeningToManagedProfileEvents) {
713                 IntentFilter intentFilter = new IntentFilter();
714                 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
715                 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
716                 context.registerReceiver(this, intentFilter);
717                 mListeningToManagedProfileEvents = true;
718             }
719         }
720 
unregister(Context context)721         public void unregister(Context context) {
722             if (mListeningToManagedProfileEvents) {
723                 context.unregisterReceiver(this);
724                 mListeningToManagedProfileEvents = false;
725             }
726         }
727     }
728 }
729