1/*
2 * Copyright (C) 2010 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
17package com.android.settings.accounts;
18
19import static android.app.Activity.RESULT_CANCELED;
20import static android.app.Activity.RESULT_OK;
21import static android.content.Intent.EXTRA_USER;
22
23import android.accounts.AccountManager;
24import android.accounts.AuthenticatorDescription;
25import android.app.Activity;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.SyncAdapterType;
30import android.content.pm.PackageManager;
31import android.content.res.Resources;
32import android.graphics.drawable.Drawable;
33import android.os.Bundle;
34import android.os.UserHandle;
35import android.os.UserManager;
36import android.support.v7.preference.Preference;
37import android.support.v7.preference.PreferenceGroup;
38import android.util.Log;
39
40import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41import com.android.internal.util.CharSequences;
42import com.android.settings.R;
43import com.android.settings.SettingsPreferenceFragment;
44import com.android.settings.Utils;
45import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
46import com.android.settings.overlay.FeatureFactory;
47import com.android.settingslib.RestrictedLockUtils;
48import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
49import com.android.settingslib.widget.FooterPreference;
50import com.android.settingslib.widget.FooterPreferenceMixin;
51
52import com.google.android.collect.Maps;
53
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.HashMap;
57import java.util.HashSet;
58import java.util.Map;
59
60/**
61 * Activity asking a user to select an account to be set up.
62 *
63 * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
64 * which the action needs to be performed is different to the one the Settings App will run in.
65 */
66public class ChooseAccountActivity extends SettingsPreferenceFragment {
67
68    private static final String TAG = "ChooseAccountActivity";
69
70    private EnterprisePrivacyFeatureProvider mFeatureProvider;
71    private FooterPreference mEnterpriseDisclosurePreference = null;
72
73    private String[] mAuthorities;
74    private PreferenceGroup mAddAccountGroup;
75    private final ArrayList<ProviderEntry> mProviderList = new ArrayList<ProviderEntry>();
76    public HashSet<String> mAccountTypesFilter;
77    private AuthenticatorDescription[] mAuthDescs;
78    private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null;
79    private Map<String, AuthenticatorDescription> mTypeToAuthDescription
80            = new HashMap<String, AuthenticatorDescription>();
81    // The UserHandle of the user we are choosing an account for
82    private UserHandle mUserHandle;
83    private UserManager mUm;
84
85    private static class ProviderEntry implements Comparable<ProviderEntry> {
86        private final CharSequence name;
87        private final String type;
88        ProviderEntry(CharSequence providerName, String accountType) {
89            name = providerName;
90            type = accountType;
91        }
92
93        public int compareTo(ProviderEntry another) {
94            if (name == null) {
95                return -1;
96            }
97            if (another.name == null) {
98                return +1;
99            }
100            return CharSequences.compareToIgnoreCase(name, another.name);
101        }
102    }
103
104    @Override
105    public int getMetricsCategory() {
106        return MetricsEvent.ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY;
107    }
108
109    @Override
110    public void onCreate(Bundle icicle) {
111        super.onCreate(icicle);
112
113        final Activity activity = getActivity();
114        mFeatureProvider = FeatureFactory.getFactory(activity)
115                .getEnterprisePrivacyFeatureProvider(activity);
116
117        addPreferencesFromResource(R.xml.add_account_settings);
118        mAuthorities = getIntent().getStringArrayExtra(
119                AccountPreferenceBase.AUTHORITIES_FILTER_KEY);
120        String[] accountTypesFilter = getIntent().getStringArrayExtra(
121                AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY);
122        if (accountTypesFilter != null) {
123            mAccountTypesFilter = new HashSet<String>();
124            for (String accountType : accountTypesFilter) {
125                mAccountTypesFilter.add(accountType);
126            }
127        }
128        mAddAccountGroup = getPreferenceScreen();
129        mUm = UserManager.get(getContext());
130        mUserHandle = Utils.getSecureTargetUser(getActivity().getActivityToken(), mUm,
131                null /* arguments */, getIntent().getExtras());
132        updateAuthDescriptions();
133    }
134
135    /**
136     * Updates provider icons. Subclasses should call this in onCreate()
137     * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
138     */
139    private void updateAuthDescriptions() {
140        mAuthDescs = AccountManager.get(getContext()).getAuthenticatorTypesAsUser(
141                mUserHandle.getIdentifier());
142        for (int i = 0; i < mAuthDescs.length; i++) {
143            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
144        }
145        onAuthDescriptionsUpdated();
146    }
147
148    private void onAuthDescriptionsUpdated() {
149        // Create list of providers to show on preference screen
150        for (int i = 0; i < mAuthDescs.length; i++) {
151            String accountType = mAuthDescs[i].type;
152            CharSequence providerName = getLabelForType(accountType);
153
154            // Skip preferences for authorities not specified. If no authorities specified,
155            // then include them all.
156            ArrayList<String> accountAuths = getAuthoritiesForAccountType(accountType);
157            boolean addAccountPref = true;
158            if (mAuthorities != null && mAuthorities.length > 0 && accountAuths != null) {
159                addAccountPref = false;
160                for (int k = 0; k < mAuthorities.length; k++) {
161                    if (accountAuths.contains(mAuthorities[k])) {
162                        addAccountPref = true;
163                        break;
164                    }
165                }
166            }
167            if (addAccountPref && mAccountTypesFilter != null
168                    && !mAccountTypesFilter.contains(accountType)) {
169                addAccountPref = false;
170            }
171            if (addAccountPref) {
172                mProviderList.add(new ProviderEntry(providerName, accountType));
173            } else {
174                if (Log.isLoggable(TAG, Log.VERBOSE)) {
175                    Log.v(TAG, "Skipped pref " + providerName + ": has no authority we need");
176                }
177            }
178        }
179
180        final Context context = getPreferenceScreen().getContext();
181        if (mProviderList.size() == 1) {
182            // There's only one provider that matches. If it is disabled by admin show the
183            // support dialog otherwise run it.
184            EnforcedAdmin admin = RestrictedLockUtils.checkIfAccountManagementDisabled(
185                    context, mProviderList.get(0).type, mUserHandle.getIdentifier());
186            if (admin != null) {
187                setResult(RESULT_CANCELED, RestrictedLockUtils.getShowAdminSupportDetailsIntent(
188                        context, admin));
189                finish();
190            } else {
191                finishWithAccountType(mProviderList.get(0).type);
192            }
193        } else if (mProviderList.size() > 0) {
194            Collections.sort(mProviderList);
195            mAddAccountGroup.removeAll();
196            for (ProviderEntry pref : mProviderList) {
197                Drawable drawable = getDrawableForType(pref.type);
198                ProviderPreference p = new ProviderPreference(getPreferenceScreen().getContext(),
199                        pref.type, drawable, pref.name);
200                p.checkAccountManagementAndSetDisabled(mUserHandle.getIdentifier());
201                mAddAccountGroup.addPreference(p);
202            }
203            addEnterpriseDisclosure();
204        } else {
205            if (Log.isLoggable(TAG, Log.VERBOSE)) {
206                final StringBuilder auths = new StringBuilder();
207                for (String a : mAuthorities) {
208                    auths.append(a);
209                    auths.append(' ');
210                }
211                Log.v(TAG, "No providers found for authorities: " + auths);
212            }
213            setResult(RESULT_CANCELED);
214            finish();
215        }
216    }
217
218    private void addEnterpriseDisclosure() {
219        final CharSequence disclosure = mFeatureProvider.getDeviceOwnerDisclosure();
220        if (disclosure == null) {
221            return;
222        }
223        if (mEnterpriseDisclosurePreference == null) {
224            mEnterpriseDisclosurePreference = mFooterPreferenceMixin.createFooterPreference();
225            mEnterpriseDisclosurePreference.setSelectable(false);
226        }
227        mEnterpriseDisclosurePreference.setTitle(disclosure);
228        mAddAccountGroup.addPreference(mEnterpriseDisclosurePreference);
229    }
230
231    public ArrayList<String> getAuthoritiesForAccountType(String type) {
232        if (mAccountTypeToAuthorities == null) {
233            mAccountTypeToAuthorities = Maps.newHashMap();
234            SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
235                    mUserHandle.getIdentifier());
236            for (int i = 0, n = syncAdapters.length; i < n; i++) {
237                final SyncAdapterType sa = syncAdapters[i];
238                ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
239                if (authorities == null) {
240                    authorities = new ArrayList<String>();
241                    mAccountTypeToAuthorities.put(sa.accountType, authorities);
242                }
243                if (Log.isLoggable(TAG, Log.VERBOSE)) {
244                    Log.d(TAG, "added authority " + sa.authority + " to accountType "
245                            + sa.accountType);
246                }
247                authorities.add(sa.authority);
248            }
249        }
250        return mAccountTypeToAuthorities.get(type);
251    }
252
253    /**
254     * Gets an icon associated with a particular account type. If none found, return null.
255     * @param accountType the type of account
256     * @return a drawable for the icon or a default icon returned by
257     * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
258     */
259    protected Drawable getDrawableForType(final String accountType) {
260        Drawable icon = null;
261        if (mTypeToAuthDescription.containsKey(accountType)) {
262            try {
263                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
264                Context authContext = getActivity()
265                        .createPackageContextAsUser(desc.packageName, 0, mUserHandle);
266                icon = getPackageManager().getUserBadgedIcon(
267                        authContext.getDrawable(desc.iconId), mUserHandle);
268            } catch (PackageManager.NameNotFoundException e) {
269                Log.w(TAG, "No icon name for account type " + accountType);
270            } catch (Resources.NotFoundException e) {
271                Log.w(TAG, "No icon resource for account type " + accountType);
272            }
273        }
274        if (icon != null) {
275            return icon;
276        } else {
277            return getPackageManager().getDefaultActivityIcon();
278        }
279    }
280
281    /**
282     * Gets the label associated with a particular account type. If none found, return null.
283     * @param accountType the type of account
284     * @return a CharSequence for the label or null if one cannot be found.
285     */
286    protected CharSequence getLabelForType(final String accountType) {
287        CharSequence label = null;
288        if (mTypeToAuthDescription.containsKey(accountType)) {
289            try {
290                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
291                Context authContext = getActivity()
292                        .createPackageContextAsUser(desc.packageName, 0, mUserHandle);
293                label = authContext.getResources().getText(desc.labelId);
294            } catch (PackageManager.NameNotFoundException e) {
295                Log.w(TAG, "No label name for account type " + accountType);
296            } catch (Resources.NotFoundException e) {
297                Log.w(TAG, "No label resource for account type " + accountType);
298            }
299        }
300        return label;
301    }
302
303    @Override
304    public boolean onPreferenceTreeClick(Preference preference) {
305        if (preference instanceof ProviderPreference) {
306            ProviderPreference pref = (ProviderPreference) preference;
307            if (Log.isLoggable(TAG, Log.VERBOSE)) {
308                Log.v(TAG, "Attempting to add account of type " + pref.getAccountType());
309            }
310            finishWithAccountType(pref.getAccountType());
311        }
312        return true;
313    }
314
315    private void finishWithAccountType(String accountType) {
316        Intent intent = new Intent();
317        intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType);
318        intent.putExtra(EXTRA_USER, mUserHandle);
319        setResult(RESULT_OK, intent);
320        finish();
321    }
322}
323