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 a default icon returned by
    100      * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
    101      */
    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      */
    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      */
    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      */
    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      */
    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 
    190     public boolean containsAccountType(String accountType) {
    191         return mTypeToAuthDescription.containsKey(accountType);
    192     }
    193 
    194     public AuthenticatorDescription getAccountTypeDescription(String accountType) {
    195         return mTypeToAuthDescription.get(accountType);
    196     }
    197 
    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 
    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
    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 
    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 
    247     public void stopListeningToAccountUpdates() {
    248         if (mListeningToAccountUpdates) {
    249             mContext.unregisterReceiver(this);
    250             mListeningToAccountUpdates = false;
    251         }
    252     }
    253 
    254     public ArrayList<String> getAuthoritiesForAccountType(String type) {
    255         return mAccountTypeToAuthorities.get(type);
    256     }
    257 
    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