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 import java.util.StringJoiner; 54 55 /** 56 * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for 57 * which the action needs to be performed is different to the one the Settings App will run in. 58 */ 59 public class ChooseAccountPreferenceController extends BasePreferenceController { 60 61 private static final String TAG = "ChooseAccountPrefCtrler"; 62 63 private final List<ProviderEntry> mProviderList; 64 private final Map<String, AuthenticatorDescription> mTypeToAuthDescription; 65 66 private String[] mAuthorities; 67 private Set<String> mAccountTypesFilter; 68 private AuthenticatorDescription[] mAuthDescs; 69 private Map<String, List<String>> mAccountTypeToAuthorities; 70 // The UserHandle of the user we are choosing an account for 71 private UserHandle mUserHandle; 72 private Activity mActivity; 73 private PreferenceScreen mScreen; 74 ChooseAccountPreferenceController(Context context, String preferenceKey)75 public ChooseAccountPreferenceController(Context context, String preferenceKey) { 76 super(context, preferenceKey); 77 78 mProviderList = new ArrayList<>(); 79 mTypeToAuthDescription = new HashMap<>(); 80 } 81 initialize(String[] authorities, String[] accountTypesFilter, UserHandle userHandle, Activity activity)82 public void initialize(String[] authorities, String[] accountTypesFilter, UserHandle userHandle, 83 Activity activity) { 84 mActivity = activity; 85 mAuthorities = authorities; 86 mUserHandle = userHandle; 87 88 if (accountTypesFilter != null) { 89 mAccountTypesFilter = new HashSet<>(); 90 for (String accountType : accountTypesFilter) { 91 mAccountTypesFilter.add(accountType); 92 } 93 } 94 } 95 96 @Override getAvailabilityStatus()97 public int getAvailabilityStatus() { 98 return AVAILABLE; 99 } 100 101 @Override displayPreference(PreferenceScreen screen)102 public void displayPreference(PreferenceScreen screen) { 103 super.displayPreference(screen); 104 mScreen = screen; 105 updateAuthDescriptions(); 106 } 107 108 @Override handlePreferenceTreeClick(Preference preference)109 public boolean handlePreferenceTreeClick(Preference preference) { 110 if (!(preference instanceof ProviderPreference)) { 111 return false; 112 } 113 114 ProviderPreference pref = (ProviderPreference) preference; 115 if (Log.isLoggable(TAG, Log.VERBOSE)) { 116 Log.v(TAG, "Attempting to add account of type " + pref.getAccountType()); 117 } 118 finishWithAccountType(pref.getAccountType()); 119 return true; 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(mContext).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 final String accountType = mAuthDescs[i].type; 139 final CharSequence providerName = getLabelForType(accountType); 140 141 // Skip preferences for authorities not specified. If no authorities specified, 142 // then include them all. 143 final List<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( 160 new ProviderEntry(providerName, accountType)); 161 } else { 162 if (Log.isLoggable(TAG, Log.VERBOSE)) { 163 Log.v(TAG, "Skipped pref " + providerName + ": has no authority we need"); 164 } 165 } 166 } 167 final Context context = mScreen.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 final RestrictedLockUtils.EnforcedAdmin admin = 172 RestrictedLockUtilsInternal.checkIfAccountManagementDisabled( 173 context, mProviderList.get(0).getType(), mUserHandle.getIdentifier()); 174 if (admin != null) { 175 mActivity.setResult(RESULT_CANCELED, 176 RestrictedLockUtils.getShowAdminSupportDetailsIntent( 177 context, admin)); 178 mActivity.finish(); 179 } else { 180 finishWithAccountType(mProviderList.get(0).getType()); 181 } 182 } else if (mProviderList.size() > 0) { 183 Collections.sort(mProviderList); 184 for (ProviderEntry pref : mProviderList) { 185 final Drawable drawable = getDrawableForType(pref.getType()); 186 final ProviderPreference p = new ProviderPreference(context, 187 pref.getType(), drawable, pref.getName()); 188 p.setKey(pref.getType().toString()); 189 p.checkAccountManagementAndSetDisabled(mUserHandle.getIdentifier()); 190 mScreen.addPreference(p); 191 } 192 } else { 193 if (mAuthorities != null && Log.isLoggable(TAG, Log.VERBOSE)) { 194 final StringBuilder auths = new StringBuilder(); 195 for (String a : mAuthorities) { 196 auths.append(a); 197 auths.append(' '); 198 } 199 Log.v(TAG, "No providers found for authorities: " + auths); 200 } 201 if (mAccountTypesFilter != null) { 202 final StringJoiner types = new StringJoiner(", ", "", ""); 203 mAccountTypesFilter.forEach(types::add); 204 Log.w(TAG, "No providers found for account types: " + types); 205 } 206 mActivity.setResult(RESULT_CANCELED); 207 // Do not finish activity to avoid the caller getting the existing account list because 208 // the prompt respond reveals that the input account does not exist. 209 } 210 } 211 getAuthoritiesForAccountType(String type)212 private List<String> getAuthoritiesForAccountType(String type) { 213 if (mAccountTypeToAuthorities == null) { 214 mAccountTypeToAuthorities = Maps.newHashMap(); 215 final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( 216 mUserHandle.getIdentifier()); 217 for (int i = 0, n = syncAdapters.length; i < n; i++) { 218 final SyncAdapterType sa = syncAdapters[i]; 219 List<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); 220 if (authorities == null) { 221 authorities = new ArrayList<>(); 222 mAccountTypeToAuthorities.put(sa.accountType, authorities); 223 } 224 if (Log.isLoggable(TAG, Log.VERBOSE)) { 225 Log.v(TAG, "added authority " + sa.authority + " to accountType " 226 + sa.accountType); 227 } 228 authorities.add(sa.authority); 229 } 230 } 231 return mAccountTypeToAuthorities.get(type); 232 } 233 234 /** 235 * Gets an icon associated with a particular account type. If none found, return null. 236 * 237 * @param accountType the type of account 238 * @return a drawable for the icon or a default icon returned by 239 * {@link PackageManager#getDefaultActivityIcon} if one cannot be found. 240 */ 241 @VisibleForTesting getDrawableForType(final String accountType)242 Drawable getDrawableForType(final String accountType) { 243 Drawable icon = null; 244 if (mTypeToAuthDescription.containsKey(accountType)) { 245 try { 246 final AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 247 final Context authContext = mActivity 248 .createPackageContextAsUser(desc.packageName, 0, mUserHandle); 249 icon = mContext.getPackageManager().getUserBadgedIcon( 250 authContext.getDrawable(desc.iconId), mUserHandle); 251 } catch (PackageManager.NameNotFoundException e) { 252 Log.w(TAG, "No icon name for account type " + accountType); 253 } catch (Resources.NotFoundException e) { 254 Log.w(TAG, "No icon resource for account type " + accountType); 255 } 256 } 257 if (icon != null) { 258 return icon; 259 } else { 260 return mContext.getPackageManager().getDefaultActivityIcon(); 261 } 262 } 263 264 /** 265 * Gets the label associated with a particular account type. If none found, return null. 266 * 267 * @param accountType the type of account 268 * @return a CharSequence for the label or null if one cannot be found. 269 */ 270 @VisibleForTesting getLabelForType(final String accountType)271 CharSequence getLabelForType(final String accountType) { 272 CharSequence label = null; 273 if (mTypeToAuthDescription.containsKey(accountType)) { 274 try { 275 final AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 276 final Context authContext = mActivity 277 .createPackageContextAsUser(desc.packageName, 0, mUserHandle); 278 label = authContext.getResources().getText(desc.labelId); 279 } catch (PackageManager.NameNotFoundException e) { 280 Log.w(TAG, "No label name for account type " + accountType); 281 } catch (Resources.NotFoundException e) { 282 Log.w(TAG, "No label resource for account type " + accountType); 283 } 284 } 285 return label; 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 mActivity.setResult(RESULT_OK, intent); 293 mActivity.finish(); 294 } 295 } 296