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