/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.accounts;

import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_CLONE;
import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CLONE_CATEGORY_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
import static android.content.Intent.EXTRA_USER;
import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
import static android.os.UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
import static android.provider.Settings.ACTION_ADD_ACCOUNT;
import static android.provider.Settings.EXTRA_AUTHORITIES;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.BidiFormatter;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.settings.AccessiblePreferenceCategory;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.search.SearchIndexableRaw;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class AccountPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin, AuthenticatorHelper.OnAccountsUpdateListener,
        OnPreferenceClickListener, LifecycleObserver, OnPause, OnResume {

    private static final String TAG = "AccountPrefController";

    private static final int ORDER_ACCOUNT_PROFILES = 101;
    private static final int ORDER_LAST = 1002;
    private static final int ORDER_NEXT_TO_LAST = 1001;
    private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;

    private static final String PREF_KEY_ADD_ACCOUNT = "add_account";
    private static final String PREF_KEY_REMOVE_PROFILE = "remove_profile";
    private static final String PREF_KEY_WORK_PROFILE_SETTING = "work_profile_setting";

    private UserManager mUm;
    private DevicePolicyManager mDpm;
    private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
    private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver =
            new ManagedProfileBroadcastReceiver();
    private Preference mProfileNotAvailablePreference;
    private String[] mAuthorities;
    private int mAuthoritiesCount = 0;
    private DashboardFragment mFragment;
    private int mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
    private AccountRestrictionHelper mHelper;
    private MetricsFeatureProvider mMetricsFeatureProvider;
    private @ProfileSelectFragment.ProfileType int mType;

    /**
     * Holds data related to the accounts belonging to one profile.
     */
    public static class ProfileData {
        /**
         * The preference that displays the accounts.
         */
        public PreferenceGroup preferenceGroup;
        /**
         * The preference that displays the add account button.
         */
        public RestrictedPreference addAccountPreference;
        /**
         * The preference that displays the button to remove the managed profile
         */
        public RestrictedPreference removeWorkProfilePreference;
        /**
         * The preference that displays managed profile settings.
         */
        public Preference managedProfilePreference;
        /**
         * The {@link AuthenticatorHelper} that holds accounts data for this profile.
         */
        public AuthenticatorHelper authenticatorHelper;
        /**
         * The {@link UserInfo} of the profile.
         */
        public UserInfo userInfo;
        /**
         * The {@link UserInfo} of the profile.
         */
        public boolean pendingRemoval;
        /**
         * The map from account key to account preference
         */
        public ArrayMap<String, AccountTypePreference> accountPreferences = new ArrayMap<>();
    }

    public AccountPreferenceController(Context context, DashboardFragment parent,
            String[] authorities, @ProfileSelectFragment.ProfileType int type) {
        this(context, parent, authorities, new AccountRestrictionHelper(context), type);
    }

    @VisibleForTesting
    AccountPreferenceController(Context context, DashboardFragment parent,
            String[] authorities, AccountRestrictionHelper helper,
            @ProfileSelectFragment.ProfileType int type) {
        super(context);
        mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
        mDpm = context.getSystemService(DevicePolicyManager.class);
        mAuthorities = authorities;
        mFragment = parent;
        if (mAuthorities != null) {
            mAuthoritiesCount = mAuthorities.length;
        }
        final FeatureFactory featureFactory = FeatureFactory.getFactory(mContext);
        mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
        mHelper = helper;
        mType = type;
    }

    @Override
    public boolean isAvailable() {
        return !mUm.isManagedProfile();
    }

    @Override
    public String getPreferenceKey() {
        return null;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        updateUi();
    }

    @Override
    public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
        if (!isAvailable()) {
            return;
        }
        final Resources res = mContext.getResources();
        final String screenTitle = res.getString(R.string.account_settings_title);

        List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
        for (final UserInfo userInfo : profiles) {
            if (userInfo.isEnabled() && userInfo.isManagedProfile()) {
                if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_MANAGED_PROFILE,
                        UserHandle.myUserId())) {
                    final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
                    data.key = PREF_KEY_REMOVE_PROFILE;
                    data.title = mDpm.getResources().getString(
                            REMOVE_WORK_PROFILE,
                            () -> res.getString(R.string.remove_managed_profile_label));
                    data.screenTitle = screenTitle;
                    rawData.add(data);
                }
                final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
                data.key = PREF_KEY_WORK_PROFILE_SETTING;
                data.title = mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
                        () -> res.getString(R.string.managed_profile_settings_title));
                data.screenTitle = screenTitle;
                rawData.add(data);
            }
        }
    }

    @Override
    public void onResume() {
        updateUi();
        mManagedProfileBroadcastReceiver.register(mContext);
        listenToAccountUpdates();
    }

    @Override
    public void onPause() {
        stopListeningToAccountUpdates();
        mManagedProfileBroadcastReceiver.unregister(mContext);
    }

    @Override
    public void onAccountsUpdate(UserHandle userHandle) {
        final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
        if (profileData != null) {
            updateAccountTypes(profileData);
        } else {
            Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
        }
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        final int metricsCategory = mFragment.getMetricsCategory();
        // Check the preference
        final int count = mProfiles.size();
        for (int i = 0; i < count; i++) {
            ProfileData profileData = mProfiles.valueAt(i);
            if (preference == profileData.addAccountPreference) {
                mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
                Intent intent = new Intent(ACTION_ADD_ACCOUNT);
                intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
                intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
                mContext.startActivity(intent);
                return true;
            }
            if (preference == profileData.removeWorkProfilePreference) {
                mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
                final int userId = profileData.userInfo.id;
                RemoveUserFragment.newInstance(userId).show(mFragment.getFragmentManager(),
                        "removeUser");
                return true;
            }
            if (preference == profileData.managedProfilePreference) {
                mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
                Bundle arguments = new Bundle();
                arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
                new SubSettingLauncher(mContext)
                        .setSourceMetricsCategory(metricsCategory)
                        .setDestination(ManagedProfileSettings.class.getName())
                        .setTitleText(mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
                                () -> mContext.getString(R.string.managed_profile_settings_title)))
                        .setArguments(arguments)
                        .launch();

                return true;
            }
        }
        return false;
    }

    private void updateUi() {
        if (!isAvailable()) {
            // This should not happen
            Log.e(TAG, "We should not be showing settings for a managed profile");
            return;
        }

        for (int i = 0, size = mProfiles.size(); i < size; i++) {
            mProfiles.valueAt(i).pendingRemoval = true;
        }
        if (mUm.isRestrictedProfile()) {
            // Restricted user or similar
            UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
            updateProfileUi(userInfo);
        } else {
            List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
            final int profilesCount = profiles.size();
            for (int i = 0; i < profilesCount; i++) {
                if (profiles.get(i).isManagedProfile()
                        && (mType & ProfileSelectFragment.ProfileType.WORK) != 0) {
                    updateProfileUi(profiles.get(i));
                } else if (!profiles.get(i).isManagedProfile()
                        && (mType & ProfileSelectFragment.ProfileType.PERSONAL) != 0) {
                    updateProfileUi(profiles.get(i));
                }
            }
        }
        cleanUpPreferences();

        // Add all preferences, starting with one for the primary profile.
        // Note that we're relying on the ordering given by the SparseArray keys, and on the
        // value of UserHandle.USER_OWNER being smaller than all the rest.
        final int profilesCount = mProfiles.size();
        for (int i = 0; i < profilesCount; i++) {
            updateAccountTypes(mProfiles.valueAt(i));
        }

        // Refresh for the auto-sync preferences
        mFragment.forceUpdatePreferences();
    }

    private void updateProfileUi(final UserInfo userInfo) {
        if (mFragment.getPreferenceManager() == null) {
            return;
        }
        final ProfileData data = mProfiles.get(userInfo.id);
        if (data != null) {
            data.pendingRemoval = false;
            data.userInfo = userInfo;
            if (userInfo.isEnabled()) {
                // recreate the authentication helper to refresh the list of enabled accounts
                data.authenticatorHelper =
                        new AuthenticatorHelper(mContext, userInfo.getUserHandle(), this);
            }
            return;
        }
        final Context context = mContext;
        final ProfileData profileData = new ProfileData();
        profileData.userInfo = userInfo;
        AccessiblePreferenceCategory preferenceGroup =
                mHelper.createAccessiblePreferenceCategory(
                        mFragment.getPreferenceManager().getContext());
        preferenceGroup.setOrder(mAccountProfileOrder++);
        preferenceGroup.setTitle(R.string.account_settings); // default title; may be modified below
        if (isSingleProfile()) {
            final String title = context.getString(R.string.account_for_section_header,
                    BidiFormatter.getInstance().unicodeWrap(userInfo.name));
            preferenceGroup.setTitle(title);
            preferenceGroup.setContentDescription(title);
        } else if (userInfo.isManagedProfile()) {
            if (mType == ProfileSelectFragment.ProfileType.ALL) {
                setCategoryTitleFromDevicePolicyResource(preferenceGroup, WORK_CATEGORY_HEADER,
                        R.string.category_work);
                final String workGroupSummary = getWorkGroupSummary(context, userInfo);
                preferenceGroup.setSummary(workGroupSummary);
                setContentDescriptionFromDevicePolicyResource(preferenceGroup,
                        ACCESSIBILITY_CATEGORY_WORK, R.string.accessibility_category_work,
                        workGroupSummary);
            }
            profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference();
            mHelper.enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
                    DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.myUserId());
            profileData.managedProfilePreference = newManagedProfileSettings();
        } else if (userInfo.isCloneProfile()) {
            if (mType == ProfileSelectFragment.ProfileType.ALL) {
                setCategoryTitleFromDevicePolicyResource(preferenceGroup, CLONE_CATEGORY_HEADER,
                        R.string.category_clone);
                setContentDescriptionFromDevicePolicyResource(preferenceGroup,
                        ACCESSIBILITY_CATEGORY_CLONE, R.string.accessibility_category_clone,
                        null);
            }
        } else {
            // Primary Profile
            if (mType == ProfileSelectFragment.ProfileType.ALL) {
                setCategoryTitleFromDevicePolicyResource(preferenceGroup, PERSONAL_CATEGORY_HEADER,
                        R.string.category_personal);
                setContentDescriptionFromDevicePolicyResource(preferenceGroup,
                        ACCESSIBILITY_CATEGORY_PERSONAL, R.string.accessibility_category_personal,
                        null);
            }
        }
        final PreferenceScreen screen = mFragment.getPreferenceScreen();
        if (screen != null) {
            screen.addPreference(preferenceGroup);
        }
        profileData.preferenceGroup = preferenceGroup;
        if (userInfo.isEnabled()) {
            profileData.authenticatorHelper = new AuthenticatorHelper(context,
                    userInfo.getUserHandle(), this);
            if (!userInfo.isCloneProfile()) {
                profileData.addAccountPreference = newAddAccountPreference();
                mHelper.enforceRestrictionOnPreference(profileData.addAccountPreference,
                        DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
            }
        }
        mProfiles.put(userInfo.id, profileData);
    }

    private void setCategoryTitleFromDevicePolicyResource(
            AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier) {
        preferenceGroup.setTitle(
                mDpm.getResources().getString(stringId,
                        () -> mContext.getString(resourceIdentifier)));
    }

    private void setContentDescriptionFromDevicePolicyResource(
            AccessiblePreferenceCategory preferenceGroup, String stringId, int resourceIdentifier,
            String formatArgs) {
        preferenceGroup.setContentDescription(mDpm.getResources().getString(stringId, () -> {
            if (formatArgs != null) {
                return mContext.getString(resourceIdentifier, formatArgs);
            }
            return mContext.getString(resourceIdentifier);
        }));
    }

    private RestrictedPreference newAddAccountPreference() {
        RestrictedPreference preference =
                new RestrictedPreference(mFragment.getPreferenceManager().getContext());
        preference.setKey(PREF_KEY_ADD_ACCOUNT);
        preference.setTitle(R.string.add_account_label);
        preference.setIcon(R.drawable.ic_add_24dp);
        preference.setOnPreferenceClickListener(this);
        preference.setOrder(ORDER_NEXT_TO_NEXT_TO_LAST);
        return preference;
    }

    private RestrictedPreference newRemoveWorkProfilePreference() {
        RestrictedPreference preference = new RestrictedPreference(
                mFragment.getPreferenceManager().getContext());
        preference.setKey(PREF_KEY_REMOVE_PROFILE);
        preference.setTitle(
                mDpm.getResources().getString(REMOVE_WORK_PROFILE,
                        () -> mContext.getString(R.string.remove_managed_profile_label)));
        preference.setIcon(R.drawable.ic_delete);
        preference.setOnPreferenceClickListener(this);
        preference.setOrder(ORDER_LAST);
        return preference;
    }

    private Preference newManagedProfileSettings() {
        Preference preference = new Preference(mFragment.getPreferenceManager().getContext());
        preference.setKey(PREF_KEY_WORK_PROFILE_SETTING);
        preference.setTitle(mDpm.getResources().getString(MANAGED_PROFILE_SETTINGS_TITLE,
                () -> mContext.getString(R.string.managed_profile_settings_title)));
        preference.setIcon(R.drawable.ic_settings_24dp);
        preference.setOnPreferenceClickListener(this);
        preference.setOrder(ORDER_NEXT_TO_LAST);
        return preference;
    }

    private String getWorkGroupSummary(Context context, UserInfo userInfo) {
        PackageManager packageManager = context.getPackageManager();
        ApplicationInfo adminApplicationInfo = Utils.getAdminApplicationInfo(context, userInfo.id);
        if (adminApplicationInfo == null) {
            return null;
        }
        CharSequence appLabel = packageManager.getApplicationLabel(adminApplicationInfo);
        return mDpm.getResources().getString(MANAGED_BY,
                () -> mContext.getString(R.string.managing_admin, appLabel), appLabel);
    }

    void cleanUpPreferences() {
        PreferenceScreen screen = mFragment.getPreferenceScreen();
        if (screen == null) {
            return;
        }
        final int count = mProfiles.size();
        for (int i = count - 1; i >= 0; i--) {
            final ProfileData data = mProfiles.valueAt(i);
            if (data.pendingRemoval) {
                screen.removePreference(data.preferenceGroup);
                mProfiles.removeAt(i);
            }
        }
    }

    private void listenToAccountUpdates() {
        final int count = mProfiles.size();
        for (int i = 0; i < count; i++) {
            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
            if (authenticatorHelper != null) {
                authenticatorHelper.listenToAccountUpdates();
            }
        }
    }

    private void stopListeningToAccountUpdates() {
        final int count = mProfiles.size();
        for (int i = 0; i < count; i++) {
            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
            if (authenticatorHelper != null) {
                authenticatorHelper.stopListeningToAccountUpdates();
            }
        }
    }

    private void updateAccountTypes(ProfileData profileData) {
        if (mFragment.getPreferenceManager() == null
                || profileData.preferenceGroup.getPreferenceManager() == null) {
            // This could happen if activity is finishing
            return;
        }
        if (profileData.userInfo.isEnabled()) {
            final ArrayMap<String, AccountTypePreference> preferenceToRemove =
                    new ArrayMap<>(profileData.accountPreferences);
            final ArrayList<AccountTypePreference> preferences = getAccountTypePreferences(
                    profileData.authenticatorHelper, profileData.userInfo.getUserHandle(),
                    preferenceToRemove);
            final int count = preferences.size();
            for (int i = 0; i < count; i++) {
                final AccountTypePreference preference = preferences.get(i);
                preference.setOrder(i);
                final String key = preference.getKey();
                if (!profileData.accountPreferences.containsKey(key)) {
                    profileData.preferenceGroup.addPreference(preference);
                    profileData.accountPreferences.put(key, preference);
                }
            }
            if (profileData.addAccountPreference != null) {
                profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
            }
            for (String key : preferenceToRemove.keySet()) {
                profileData.preferenceGroup.removePreference(
                        profileData.accountPreferences.get(key));
                profileData.accountPreferences.remove(key);
            }
        } else {
            profileData.preferenceGroup.removeAll();
            // Put a label instead of the accounts list
            if (mProfileNotAvailablePreference == null) {
                mProfileNotAvailablePreference =
                        new Preference(mFragment.getPreferenceManager().getContext());
            }
            mProfileNotAvailablePreference.setEnabled(false);
            mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
            mProfileNotAvailablePreference.setTitle(null);
            mProfileNotAvailablePreference.setSummary(
                    mDpm.getResources().getString(
                            WORK_PROFILE_NOT_AVAILABLE, () -> mContext.getString(
                    R.string.managed_profile_not_available_label)));
            profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
        }
        if (profileData.removeWorkProfilePreference != null) {
            profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
        }
        if (profileData.managedProfilePreference != null) {
            profileData.preferenceGroup.addPreference(profileData.managedProfilePreference);
        }
    }

    private ArrayList<AccountTypePreference> getAccountTypePreferences(AuthenticatorHelper helper,
            UserHandle userHandle, ArrayMap<String, AccountTypePreference> preferenceToRemove) {
        final String[] accountTypes = helper.getEnabledAccountTypes();
        final ArrayList<AccountTypePreference> accountTypePreferences =
                new ArrayList<>(accountTypes.length);

        for (int i = 0; i < accountTypes.length; i++) {
            final String accountType = accountTypes[i];
            // Skip showing any account that does not have any of the requested authorities
            if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
                continue;
            }
            final CharSequence label = helper.getLabelForType(mContext, accountType);
            if (label == null) {
                continue;
            }
            final String titleResPackageName = helper.getPackageForType(accountType);
            final int titleResId = helper.getLabelIdForType(accountType);

            final Account[] accounts = AccountManager.get(mContext)
                    .getAccountsByTypeAsUser(accountType, userHandle);
            final Drawable icon = helper.getDrawableForType(mContext, accountType);
            final Context prefContext = mFragment.getPreferenceManager().getContext();

            // Add a preference row for each individual account
            for (Account account : accounts) {
                final AccountTypePreference preference =
                        preferenceToRemove.remove(AccountTypePreference.buildKey(account));
                if (preference != null) {
                    accountTypePreferences.add(preference);
                    continue;
                }
                final ArrayList<String> auths =
                        helper.getAuthoritiesForAccountType(account.type);
                if (!AccountRestrictionHelper.showAccount(mAuthorities, auths)) {
                    continue;
                }
                final Bundle fragmentArguments = new Bundle();
                fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT,
                        account);
                fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE,
                        userHandle);
                fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE,
                        accountType);
                fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL,
                        label.toString());
                fragmentArguments.putInt(AccountDetailDashboardFragment.KEY_ACCOUNT_TITLE_RES,
                        titleResId);
                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
                accountTypePreferences.add(new AccountTypePreference(
                        prefContext, mMetricsFeatureProvider.getMetricsCategory(mFragment),
                        account, titleResPackageName, titleResId, label,
                        AccountDetailDashboardFragment.class.getName(), fragmentArguments, icon));
            }
            helper.preloadDrawableForType(mContext, accountType);
        }
        // Sort by label
        Collections.sort(accountTypePreferences, new Comparator<AccountTypePreference>() {
            @Override
            public int compare(AccountTypePreference t1, AccountTypePreference t2) {
                int result = t1.getSummary().toString().compareTo(t2.getSummary().toString());
                return result != 0
                        ? result : t1.getTitle().toString().compareTo(t2.getTitle().toString());
            }
        });
        return accountTypePreferences;
    }

    private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
            String accountType) {
        if (mAuthoritiesCount == 0) {
            // No authorities required
            return true;
        }
        final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
                accountType);
        if (authoritiesForType == null) {
            Log.d(TAG, "No sync authorities for account type: " + accountType);
            return false;
        }
        for (int j = 0; j < mAuthoritiesCount; j++) {
            if (authoritiesForType.contains(mAuthorities[j])) {
                return true;
            }
        }
        return false;
    }

    private boolean isSingleProfile() {
        return mUm.isLinkedUser() || mUm.getProfiles(UserHandle.myUserId()).size() == 1;
    }

    private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
        private boolean mListeningToManagedProfileEvents;

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            Log.v(TAG, "Received broadcast: " + action);
            if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
                    || action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
                if (mFragment instanceof AccountWorkProfileDashboardFragment) {
                    new SubSettingLauncher(context)
                            .setDestination(AccountDashboardFragment.class.getName())
                            .setSourceMetricsCategory(mFragment.getMetricsCategory())
                            .setTitleRes(-1)
                            .setIsSecondLayerPage(true)
                            .launch();
                    mFragment.getActivity().finish();
                } else {
                    // Clean old state
                    stopListeningToAccountUpdates();
                    // Build new state
                    updateUi();
                    listenToAccountUpdates();
                }
                return;
            }
            Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
        }

        public void register(Context context) {
            if (!mListeningToManagedProfileEvents) {
                IntentFilter intentFilter = new IntentFilter();
                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
                context.registerReceiver(this, intentFilter);
                mListeningToManagedProfileEvents = true;
            }
        }

        public void unregister(Context context) {
            if (mListeningToManagedProfileEvents) {
                context.unregisterReceiver(this);
                mListeningToManagedProfileEvents = false;
            }
        }
    }
}
