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