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 { 68 void onAccountsUpdate(UserHandle userHandle); 69 } 70 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 82 public String[] getEnabledAccountTypes() { 83 return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]); 84 } 85 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 null if one cannot be found. 100 */ 101 public Drawable getDrawableForType(Context context, final String accountType) { 102 Drawable icon = null; 103 synchronized (mAccTypeIconCache) { 104 if (mAccTypeIconCache.containsKey(accountType)) { 105 return mAccTypeIconCache.get(accountType); 106 } 107 } 108 if (mTypeToAuthDescription.containsKey(accountType)) { 109 try { 110 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 111 Context authContext = context.createPackageContextAsUser(desc.packageName, 0, 112 mUserHandle); 113 icon = mContext.getPackageManager().getUserBadgedIcon( 114 authContext.getResources().getDrawable(desc.iconId), mUserHandle); 115 synchronized (mAccTypeIconCache) { 116 mAccTypeIconCache.put(accountType, icon); 117 } 118 } catch (PackageManager.NameNotFoundException e) { 119 } catch (Resources.NotFoundException e) { 120 } 121 } 122 if (icon == null) { 123 icon = context.getPackageManager().getDefaultActivityIcon(); 124 } 125 return icon; 126 } 127 128 /** 129 * Gets the label associated with a particular account type. If none found, return null. 130 * @param accountType the type of account 131 * @return a CharSequence for the label or null if one cannot be found. 132 */ 133 public CharSequence getLabelForType(Context context, final String accountType) { 134 CharSequence label = null; 135 if (mTypeToAuthDescription.containsKey(accountType)) { 136 try { 137 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); 138 Context authContext = context.createPackageContextAsUser(desc.packageName, 0, 139 mUserHandle); 140 label = authContext.getResources().getText(desc.labelId); 141 } catch (PackageManager.NameNotFoundException e) { 142 Log.w(TAG, "No label name for account type " + accountType); 143 } catch (Resources.NotFoundException e) { 144 Log.w(TAG, "No label icon for account type " + accountType); 145 } 146 } 147 return label; 148 } 149 150 /** 151 * Updates provider icons. Subclasses should call this in onCreate() 152 * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). 153 */ 154 public void updateAuthDescriptions(Context context) { 155 mAuthDescs = AccountManager.get(context) 156 .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier()); 157 for (int i = 0; i < mAuthDescs.length; i++) { 158 mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); 159 } 160 } 161 162 public boolean containsAccountType(String accountType) { 163 return mTypeToAuthDescription.containsKey(accountType); 164 } 165 166 public AuthenticatorDescription getAccountTypeDescription(String accountType) { 167 return mTypeToAuthDescription.get(accountType); 168 } 169 170 public boolean hasAccountPreferences(final String accountType) { 171 if (containsAccountType(accountType)) { 172 AuthenticatorDescription desc = getAccountTypeDescription(accountType); 173 if (desc != null && desc.accountPreferencesId != 0) { 174 return true; 175 } 176 } 177 return false; 178 } 179 180 void onAccountsUpdated(Account[] accounts) { 181 updateAuthDescriptions(mContext); 182 if (accounts == null) { 183 accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier()); 184 } 185 mEnabledAccountTypes.clear(); 186 mAccTypeIconCache.clear(); 187 for (int i = 0; i < accounts.length; i++) { 188 final Account account = accounts[i]; 189 if (!mEnabledAccountTypes.contains(account.type)) { 190 mEnabledAccountTypes.add(account.type); 191 } 192 } 193 buildAccountTypeToAuthoritiesMap(); 194 if (mListeningToAccountUpdates) { 195 mListener.onAccountsUpdate(mUserHandle); 196 } 197 } 198 199 @Override 200 public void onReceive(final Context context, final Intent intent) { 201 // TODO: watch for package upgrades to invalidate cache; see http://b/7206643 202 final Account[] accounts = AccountManager.get(mContext) 203 .getAccountsAsUser(mUserHandle.getIdentifier()); 204 onAccountsUpdated(accounts); 205 } 206 207 public void listenToAccountUpdates() { 208 if (!mListeningToAccountUpdates) { 209 IntentFilter intentFilter = new IntentFilter(); 210 intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); 211 // At disk full, certain actions are blocked (such as writing the accounts to storage). 212 // It is useful to also listen for recovery from disk full to avoid bugs. 213 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 214 mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null); 215 mListeningToAccountUpdates = true; 216 } 217 } 218 219 public void stopListeningToAccountUpdates() { 220 if (mListeningToAccountUpdates) { 221 mContext.unregisterReceiver(this); 222 mListeningToAccountUpdates = false; 223 } 224 } 225 226 public ArrayList<String> getAuthoritiesForAccountType(String type) { 227 return mAccountTypeToAuthorities.get(type); 228 } 229 230 private void buildAccountTypeToAuthoritiesMap() { 231 mAccountTypeToAuthorities.clear(); 232 SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( 233 mUserHandle.getIdentifier()); 234 for (int i = 0, n = syncAdapters.length; i < n; i++) { 235 final SyncAdapterType sa = syncAdapters[i]; 236 ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType); 237 if (authorities == null) { 238 authorities = new ArrayList<String>(); 239 mAccountTypeToAuthorities.put(sa.accountType, authorities); 240 } 241 if (Log.isLoggable(TAG, Log.VERBOSE)) { 242 Log.d(TAG, "Added authority " + sa.authority + " to accountType " 243 + sa.accountType); 244 } 245 authorities.add(sa.authority); 246 } 247 } 248 } 249