1 /* 2 * Copyright (C) 2012 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 com.google.android.collect.Maps; 20 21 import android.accounts.Account; 22 import android.accounts.AccountManager; 23 import android.accounts.AuthenticatorDescription; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 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.AsyncTask; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.util.Log; 37 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.Map; 41 42 /** 43 * Helper class for monitoring accounts on the device for a given user. 44 * 45 * Classes using this helper should implement {@link OnAccountsUpdateListener}. 46 * {@link OnAccountsUpdateListener#onAccountsUpdate(UserHandle)} will then be 47 * called once accounts get updated. For setting up listening for account 48 * updates, {@link #listenToAccountUpdates()} and 49 * {@link #stopListeningToAccountUpdates()} should be used. 50 */ 51 final public class AuthenticatorHelper extends BroadcastReceiver { 52 private static final String TAG = "AuthenticatorHelper"; 53 54 private Map<String, AuthenticatorDescription> mTypeToAuthDescription 55 = new HashMap<String, AuthenticatorDescription>(); 56 private AuthenticatorDescription[] mAuthDescs; 57 private ArrayList<String> mEnabledAccountTypes = new ArrayList<String>(); 58 private Map<String, Drawable> mAccTypeIconCache = new HashMap<String, Drawable>(); 59 private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = Maps.newHashMap(); 60 61 private final UserHandle mUserHandle; 62 private final UserManager mUm; 63 private final Context mContext; 64 private final OnAccountsUpdateListener mListener; 65 private boolean mListeningToAccountUpdates; 66 67 public interface OnAccountsUpdateListener { onAccountsUpdate(UserHandle userHandle)68 void onAccountsUpdate(UserHandle userHandle); 69 } 70 AuthenticatorHelper(Context context, UserHandle userHandle, UserManager userManager, OnAccountsUpdateListener listener)71 public AuthenticatorHelper(Context context, UserHandle userHandle, UserManager userManager, 72 OnAccountsUpdateListener listener) { 73 mContext = context; 74 mUm = userManager; 75 mUserHandle = userHandle; 76 mListener = listener; 77 // This guarantees that the helper is ready to use once constructed: the account types and 78 // authorities are initialized 79 onAccountsUpdated(null); 80 } 81 getEnabledAccountTypes()82 public String[] getEnabledAccountTypes() { 83 return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]); 84 } 85 preloadDrawableForType(final Context context, final String accountType)86 public void preloadDrawableForType(final Context context, final String accountType) { 87 new AsyncTask<Void, Void, Void>() { 88 @Override 89 protected Void doInBackground(Void... params) { 90 getDrawableForType(context, accountType); 91 return null; 92 } 93 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); 94 } 95 96 /** 97 * Gets an icon associated with a particular account type. If none found, return null. 98 * @param accountType the type of account 99 * @return a drawable for the icon or a default icon returned by 100 * {@link PackageManager#getDefaultActivityIcon} if one cannot be found. 101 */ getDrawableForType(Context context, final String accountType)102 public Drawable getDrawableForType(Context context, final String accountType) { 103 Drawable icon = null; 104 synchronized (mAccTypeIconCache) { 105 if (mAccTypeIconCache.containsKey(accountType)) { 106 return mAccTypeIconCache.get(accountType); 107 } 108 } 109 if (mTypeToAuthDescription.containsKey(accountType)) { 110 try { 111 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 112 Context authContext = context.createPackageContextAsUser(desc.packageName, 0, 113 mUserHandle); 114 icon = mContext.getPackageManager().getUserBadgedIcon( 115 authContext.getDrawable(desc.iconId), mUserHandle); 116 synchronized (mAccTypeIconCache) { 117 mAccTypeIconCache.put(accountType, icon); 118 } 119 } catch (PackageManager.NameNotFoundException e) { 120 } catch (Resources.NotFoundException e) { 121 } 122 } 123 if (icon == null) { 124 icon = context.getPackageManager().getDefaultActivityIcon(); 125 } 126 return icon; 127 } 128 129 /** 130 * Gets the label associated with a particular account type. If none found, return null. 131 * @param accountType the type of account 132 * @return a CharSequence for the label or null if one cannot be found. 133 */ getLabelForType(Context context, final String accountType)134 public CharSequence getLabelForType(Context context, final String accountType) { 135 CharSequence label = null; 136 if (mTypeToAuthDescription.containsKey(accountType)) { 137 try { 138 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 139 Context authContext = context.createPackageContextAsUser(desc.packageName, 0, 140 mUserHandle); 141 label = authContext.getResources().getText(desc.labelId); 142 } catch (PackageManager.NameNotFoundException e) { 143 Log.w(TAG, "No label name for account type " + accountType); 144 } catch (Resources.NotFoundException e) { 145 Log.w(TAG, "No label icon for account type " + accountType); 146 } 147 } 148 return label; 149 } 150 151 /** 152 * Gets the package associated with a particular account type. If none found, return null. 153 * @param accountType the type of account 154 * @return the package name or null if one cannot be found. 155 */ getPackageForType(final String accountType)156 public String getPackageForType(final String accountType) { 157 if (mTypeToAuthDescription.containsKey(accountType)) { 158 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 159 return desc.packageName; 160 } 161 return null; 162 } 163 164 /** 165 * Gets the resource id of the label associated with a particular account type. If none found, 166 * return -1. 167 * @param accountType the type of account 168 * @return a resource id for the label or -1 if none found; 169 */ getLabelIdForType(final String accountType)170 public int getLabelIdForType(final String accountType) { 171 if (mTypeToAuthDescription.containsKey(accountType)) { 172 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 173 return desc.labelId; 174 } 175 return -1; 176 } 177 178 /** 179 * Updates provider icons. Subclasses should call this in onCreate() 180 * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). 181 */ updateAuthDescriptions(Context context)182 public void updateAuthDescriptions(Context context) { 183 mAuthDescs = AccountManager.get(context) 184 .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier()); 185 for (int i = 0; i < mAuthDescs.length; i++) { 186 mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); 187 } 188 } 189 containsAccountType(String accountType)190 public boolean containsAccountType(String accountType) { 191 return mTypeToAuthDescription.containsKey(accountType); 192 } 193 getAccountTypeDescription(String accountType)194 public AuthenticatorDescription getAccountTypeDescription(String accountType) { 195 return mTypeToAuthDescription.get(accountType); 196 } 197 hasAccountPreferences(final String accountType)198 public boolean hasAccountPreferences(final String accountType) { 199 if (containsAccountType(accountType)) { 200 AuthenticatorDescription desc = getAccountTypeDescription(accountType); 201 if (desc != null && desc.accountPreferencesId != 0) { 202 return true; 203 } 204 } 205 return false; 206 } 207 onAccountsUpdated(Account[] accounts)208 void onAccountsUpdated(Account[] accounts) { 209 updateAuthDescriptions(mContext); 210 if (accounts == null) { 211 accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier()); 212 } 213 mEnabledAccountTypes.clear(); 214 mAccTypeIconCache.clear(); 215 for (int i = 0; i < accounts.length; i++) { 216 final Account account = accounts[i]; 217 if (!mEnabledAccountTypes.contains(account.type)) { 218 mEnabledAccountTypes.add(account.type); 219 } 220 } 221 buildAccountTypeToAuthoritiesMap(); 222 if (mListeningToAccountUpdates) { 223 mListener.onAccountsUpdate(mUserHandle); 224 } 225 } 226 227 @Override onReceive(final Context context, final Intent intent)228 public void onReceive(final Context context, final Intent intent) { 229 // TODO: watch for package upgrades to invalidate cache; see http://b/7206643 230 final Account[] accounts = AccountManager.get(mContext) 231 .getAccountsAsUser(mUserHandle.getIdentifier()); 232 onAccountsUpdated(accounts); 233 } 234 listenToAccountUpdates()235 public void listenToAccountUpdates() { 236 if (!mListeningToAccountUpdates) { 237 IntentFilter intentFilter = new IntentFilter(); 238 intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); 239 // At disk full, certain actions are blocked (such as writing the accounts to storage). 240 // It is useful to also listen for recovery from disk full to avoid bugs. 241 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 242 mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null); 243 mListeningToAccountUpdates = true; 244 } 245 } 246 stopListeningToAccountUpdates()247 public void stopListeningToAccountUpdates() { 248 if (mListeningToAccountUpdates) { 249 mContext.unregisterReceiver(this); 250 mListeningToAccountUpdates = false; 251 } 252 } 253 getAuthoritiesForAccountType(String type)254 public ArrayList<String> getAuthoritiesForAccountType(String type) { 255 return mAccountTypeToAuthorities.get(type); 256 } 257 buildAccountTypeToAuthoritiesMap()258 private void buildAccountTypeToAuthoritiesMap() { 259 mAccountTypeToAuthorities.clear(); 260 SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( 261 mUserHandle.getIdentifier()); 262 for (int i = 0, n = syncAdapters.length; i < n; i++) { 263 final SyncAdapterType sa = syncAdapters[i]; 264 ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); 265 if (authorities == null) { 266 authorities = new ArrayList<String>(); 267 mAccountTypeToAuthorities.put(sa.accountType, authorities); 268 } 269 if (Log.isLoggable(TAG, Log.VERBOSE)) { 270 Log.d(TAG, "Added authority " + sa.authority + " to accountType " 271 + sa.accountType); 272 } 273 authorities.add(sa.authority); 274 } 275 } 276 } 277