Home | History | Annotate | Download | only in accounts
      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