From d1e32720441dd28a2ceed6a0d058f367e71421ec Mon Sep 17 00:00:00 2001 From: Tony Mantler Date: Tue, 17 Nov 2015 15:14:00 -0800 Subject: [PATCH] Move AnimatedImageView and AuthenticatorHelper to SettingsLib Change-Id: If3fef35b88b041326c510064679e92a90f31c6b2 --- .../accounts/AuthenticatorHelper.java | 269 ++++++++++++++++++ .../settingslib/widget/AnimatedImageView.java | 100 +++++++ 2 files changed, 369 insertions(+) create mode 100644 packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java diff --git a/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java new file mode 100644 index 0000000000000..ef511bbc81331 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settingslib.accounts; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SyncAdapterType; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.UserHandle; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class for monitoring accounts on the device for a given user. + * + * Classes using this helper should implement {@link OnAccountsUpdateListener}. + * {@link OnAccountsUpdateListener#onAccountsUpdate(UserHandle)} will then be + * called once accounts get updated. For setting up listening for account + * updates, {@link #listenToAccountUpdates()} and + * {@link #stopListeningToAccountUpdates()} should be used. + */ +final public class AuthenticatorHelper extends BroadcastReceiver { + private static final String TAG = "AuthenticatorHelper"; + + private final Map mTypeToAuthDescription = new HashMap<>(); + private final ArrayList mEnabledAccountTypes = new ArrayList<>(); + private final Map mAccTypeIconCache = new HashMap<>(); + private final HashMap> mAccountTypeToAuthorities = new HashMap<>(); + + private final UserHandle mUserHandle; + private final Context mContext; + private final OnAccountsUpdateListener mListener; + private boolean mListeningToAccountUpdates; + + public interface OnAccountsUpdateListener { + void onAccountsUpdate(UserHandle userHandle); + } + + public AuthenticatorHelper(Context context, UserHandle userHandle, + OnAccountsUpdateListener listener) { + mContext = context; + mUserHandle = userHandle; + mListener = listener; + // This guarantees that the helper is ready to use once constructed: the account types and + // authorities are initialized + onAccountsUpdated(null); + } + + public String[] getEnabledAccountTypes() { + return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]); + } + + public void preloadDrawableForType(final Context context, final String accountType) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + getDrawableForType(context, accountType); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + /** + * Gets an icon associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a drawable for the icon or a default icon returned by + * {@link PackageManager#getDefaultActivityIcon} if one cannot be found. + */ + public Drawable getDrawableForType(Context context, final String accountType) { + Drawable icon = null; + synchronized (mAccTypeIconCache) { + if (mAccTypeIconCache.containsKey(accountType)) { + return mAccTypeIconCache.get(accountType); + } + } + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = context.createPackageContextAsUser(desc.packageName, 0, + mUserHandle); + icon = mContext.getPackageManager().getUserBadgedIcon( + authContext.getDrawable(desc.iconId), mUserHandle); + synchronized (mAccTypeIconCache) { + mAccTypeIconCache.put(accountType, icon); + } + } catch (PackageManager.NameNotFoundException|Resources.NotFoundException e) { + // Ignore + } + } + if (icon == null) { + icon = context.getPackageManager().getDefaultActivityIcon(); + } + return icon; + } + + /** + * Gets the label associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a CharSequence for the label or null if one cannot be found. + */ + public CharSequence getLabelForType(Context context, final String accountType) { + CharSequence label = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = context.createPackageContextAsUser(desc.packageName, 0, + mUserHandle); + label = authContext.getResources().getText(desc.labelId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "No label name for account type " + accountType); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "No label icon for account type " + accountType); + } + } + return label; + } + + /** + * Gets the package associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return the package name or null if one cannot be found. + */ + public String getPackageForType(final String accountType) { + if (mTypeToAuthDescription.containsKey(accountType)) { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + return desc.packageName; + } + return null; + } + + /** + * Gets the resource id of the label associated with a particular account type. If none found, + * return -1. + * @param accountType the type of account + * @return a resource id for the label or -1 if none found; + */ + public int getLabelIdForType(final String accountType) { + if (mTypeToAuthDescription.containsKey(accountType)) { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + return desc.labelId; + } + return -1; + } + + /** + * Updates provider icons. Subclasses should call this in onCreate() + * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). + */ + public void updateAuthDescriptions(Context context) { + AuthenticatorDescription[] authDescs = AccountManager.get(context) + .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier()); + for (int i = 0; i < authDescs.length; i++) { + mTypeToAuthDescription.put(authDescs[i].type, authDescs[i]); + } + } + + public boolean containsAccountType(String accountType) { + return mTypeToAuthDescription.containsKey(accountType); + } + + public AuthenticatorDescription getAccountTypeDescription(String accountType) { + return mTypeToAuthDescription.get(accountType); + } + + public boolean hasAccountPreferences(final String accountType) { + if (containsAccountType(accountType)) { + AuthenticatorDescription desc = getAccountTypeDescription(accountType); + if (desc != null && desc.accountPreferencesId != 0) { + return true; + } + } + return false; + } + + void onAccountsUpdated(Account[] accounts) { + updateAuthDescriptions(mContext); + if (accounts == null) { + accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier()); + } + mEnabledAccountTypes.clear(); + mAccTypeIconCache.clear(); + for (int i = 0; i < accounts.length; i++) { + final Account account = accounts[i]; + if (!mEnabledAccountTypes.contains(account.type)) { + mEnabledAccountTypes.add(account.type); + } + } + buildAccountTypeToAuthoritiesMap(); + if (mListeningToAccountUpdates) { + mListener.onAccountsUpdate(mUserHandle); + } + } + + @Override + public void onReceive(final Context context, final Intent intent) { + // TODO: watch for package upgrades to invalidate cache; see http://b/7206643 + final Account[] accounts = AccountManager.get(mContext) + .getAccountsAsUser(mUserHandle.getIdentifier()); + onAccountsUpdated(accounts); + } + + public void listenToAccountUpdates() { + if (!mListeningToAccountUpdates) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); + // At disk full, certain actions are blocked (such as writing the accounts to storage). + // It is useful to also listen for recovery from disk full to avoid bugs. + intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null); + mListeningToAccountUpdates = true; + } + } + + public void stopListeningToAccountUpdates() { + if (mListeningToAccountUpdates) { + mContext.unregisterReceiver(this); + mListeningToAccountUpdates = false; + } + } + + public ArrayList getAuthoritiesForAccountType(String type) { + return mAccountTypeToAuthorities.get(type); + } + + private void buildAccountTypeToAuthoritiesMap() { + mAccountTypeToAuthorities.clear(); + SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( + mUserHandle.getIdentifier()); + for (int i = 0, n = syncAdapters.length; i < n; i++) { + final SyncAdapterType sa = syncAdapters[i]; + ArrayList authorities = mAccountTypeToAuthorities.get(sa.accountType); + if (authorities == null) { + authorities = new ArrayList(); + mAccountTypeToAuthorities.put(sa.accountType, authorities); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Added authority " + sa.authority + " to accountType " + + sa.accountType); + } + authorities.add(sa.authority); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java new file mode 100644 index 0000000000000..f5e39bef3d638 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settingslib.widget; + +import android.content.Context; +import android.graphics.drawable.AnimatedRotateDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +public class AnimatedImageView extends ImageView { + private AnimatedRotateDrawable mDrawable; + private boolean mAnimating; + + public AnimatedImageView(Context context) { + super(context); + } + + public AnimatedImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void updateDrawable() { + if (isShown() && mDrawable != null) { + mDrawable.stop(); + } + final Drawable drawable = getDrawable(); + if (drawable instanceof AnimatedRotateDrawable) { + mDrawable = (AnimatedRotateDrawable) drawable; + // TODO: define in drawable xml once we have public attrs. + mDrawable.setFramesCount(56); + mDrawable.setFramesDuration(32); + if (isShown() && mAnimating) { + mDrawable.start(); + } + } else { + mDrawable = null; + } + } + + private void updateAnimating() { + if (mDrawable != null) { + if (isShown() && mAnimating) { + mDrawable.start(); + } else { + mDrawable.stop(); + } + } + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + updateDrawable(); + } + + @Override + public void setImageResource(int resid) { + super.setImageResource(resid); + updateDrawable(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + updateAnimating(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + updateAnimating(); + } + + public void setAnimating(boolean animating) { + mAnimating = animating; + updateAnimating(); + } + + @Override + protected void onVisibilityChanged(View changedView, int vis) { + super.onVisibilityChanged(changedView, vis); + updateAnimating(); + } +}