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