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