/* * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com> * All Rights Reserved. */ package me.zhanghai.android.douya.account.util; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OnAccountsUpdateListener; import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import java.io.IOException; import me.zhanghai.android.douya.DouyaApplication; import me.zhanghai.android.douya.account.app.AccountPreferences; import me.zhanghai.android.douya.account.info.AccountContract; import me.zhanghai.android.douya.account.ui.AddAccountActivity; import me.zhanghai.android.douya.account.ui.SelectAccountActivity; import me.zhanghai.android.douya.network.Volley; import me.zhanghai.android.douya.network.api.info.apiv2.User; import me.zhanghai.android.douya.settings.info.Settings; import me.zhanghai.android.douya.util.GsonHelper; public class AccountUtils { public static AccountManager getAccountManager() { return AccountManager.get(DouyaApplication.getInstance()); } public static AccountManagerFuture<Bundle> addAccount(Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { return getAccountManager().addAccount(AccountContract.ACCOUNT_TYPE, AccountContract.AUTH_TOKEN_TYPE_FRODO, null, null, activity, callback, handler); } public static AccountManagerFuture<Bundle> addAccount(Activity activity) { return addAccount(activity, null, null); } public static void addAccount(Activity activity, Intent onAddedIntent) { activity.startActivity(AddAccountActivity.makeIntent(onAddedIntent, activity)); } public static boolean addAccountExplicitly(Account account, String password) { return getAccountManager().addAccountExplicitly(account, password, null); } public static AccountManagerFuture<Bundle> updatePassword(Activity activity, Account account, AccountManagerCallback<Bundle> callback, Handler handler) { return getAccountManager().updateCredentials(account, AccountContract.AUTH_TOKEN_TYPE_FRODO, null, activity, callback, handler); } public static AccountManagerFuture<Bundle> updatePassword(Activity activity, Account account) { return updatePassword(activity, account, null, null); } public static AccountManagerFuture<Bundle> confirmPassword(Activity activity, Account account, AccountManagerCallback<Bundle> callback, Handler handler) { return getAccountManager().confirmCredentials(account, null, activity, callback, handler); } public interface ConfirmPasswordListener { void onConfirmed(); void onFailed(); } private static AccountManagerCallback<Bundle> makeConfirmPasswordCallback( final ConfirmPasswordListener listener) { return new AccountManagerCallback<Bundle>() { @Override public void run(AccountManagerFuture<Bundle> future) { try { boolean confirmed = future.getResult() .getBoolean(AccountManager.KEY_BOOLEAN_RESULT); if (confirmed) { listener.onConfirmed(); } else { listener.onFailed(); } } catch (AuthenticatorException | IOException | OperationCanceledException e) { e.printStackTrace(); listener.onFailed(); } } }; } public static void confirmPassword(Activity activity, Account account, final ConfirmPasswordListener listener, Handler handler) { confirmPassword(activity, account, makeConfirmPasswordCallback(listener), handler); } public static void confirmPassword(Activity activity, final ConfirmPasswordListener listener) { confirmPassword(activity, getActiveAccount(), listener, null); } // REMOVEME: This seems infeasible. And we should check against local password instead of using // network public static Intent makeConfirmPasswordIntent(Account account, final ConfirmPasswordListener listener) { try { return confirmPassword(null, account, makeConfirmPasswordCallback(listener), null) .getResult().getParcelable(AccountManager.KEY_INTENT); } catch (AuthenticatorException | IOException | OperationCanceledException e) { e.printStackTrace(); return null; } } public static Intent makeConfirmPasswordIntent(final ConfirmPasswordListener listener) { return makeConfirmPasswordIntent(getActiveAccount(), listener); } public static void addOnAccountListUpdatedListener(OnAccountsUpdateListener listener) { getAccountManager().addOnAccountsUpdatedListener(listener, null, false); } public static void removeOnAccountListUpdatedListener(OnAccountsUpdateListener listener) { getAccountManager().removeOnAccountsUpdatedListener(listener); } public static Account[] getAccounts() { return getAccountManager().getAccountsByType(AccountContract.ACCOUNT_TYPE); } private static Account getAccountByName(String accountName) { if (TextUtils.isEmpty(accountName)) { return null; } for (Account account : getAccounts()) { if (TextUtils.equals(account.name, accountName)) { return account; } } return null; } // NOTE: This method is asynchronous. public static void removeAccount(Account account) { //noinspection deprecation getAccountManager().removeAccount(account, null, null); } public static boolean hasAccount() { return getAccounts().length != 0; } // NOTE: Use getActiveAccount() instead for availability checking. private static String getActiveAccountName() { return Settings.ACTIVE_ACCOUNT_NAME.getValue(); } private static void setActiveAccountName(String accountName) { Settings.ACTIVE_ACCOUNT_NAME.putValue(accountName); } private static void removeActiveAccountName() { Settings.ACTIVE_ACCOUNT_NAME.remove(); } public static boolean hasActiveAccountName() { return !TextUtils.isEmpty(getActiveAccountName()); } public static boolean isActiveAccountName(String accountName) { return TextUtils.equals(accountName, getActiveAccountName()); } // NOTICE: // Will clear the invalid setting and return null if no matching account with the name from // setting is found. public static Account getActiveAccount() { Account account = getAccountByName(getActiveAccountName()); if (account != null) { return account; } else { removeActiveAccountName(); return null; } } public static void setActiveAccount(Account account) { if (account == null) { removeActiveAccountName(); return; } Account oldActiveAccount = getActiveAccount(); setActiveAccountName(account.name); if (oldActiveAccount != null) { if (TextUtils.equals(getRecentOneAccountName(), account.name)) { setRecentOneAccountName(oldActiveAccount.name); } else if (TextUtils.equals(getRecentTwoAccountName(), account.name)) { setRecentTwoAccountName(oldActiveAccount.name); } else { setRecentTwoAccountName(getRecentOneAccountName()); setRecentOneAccountName(oldActiveAccount.name); } } Volley.getInstance().notifyActiveAccountChanged(); } public static boolean hasActiveAccount() { return getActiveAccount() != null; } public static boolean isActiveAccount(Account account) { return isActiveAccountName(account.name); } private static String getRecentOneAccountName() { return Settings.RECENT_ONE_ACCOUNT_NAME.getValue(); } private static void setRecentOneAccountName(String accountName) { Settings.RECENT_ONE_ACCOUNT_NAME.putValue(accountName); } private static void removeRecentOneAccountName() { Settings.RECENT_ONE_ACCOUNT_NAME.remove(); } public static Account getRecentOneAccount() { Account activeAccount = getActiveAccount(); if (activeAccount == null) { return null; } String accountName = getRecentOneAccountName(); if (!TextUtils.isEmpty(accountName) && !TextUtils.equals(accountName, activeAccount.name)) { Account account = getAccountByName(accountName); if (account != null) { return account; } } for (Account account : getAccounts()) { if (!account.equals(activeAccount)) { setRecentOneAccountName(account.name); return account; } } removeRecentOneAccountName(); return null; } private static String getRecentTwoAccountName() { return Settings.RECENT_TWO_ACCOUNT_NAME.getValue(); } private static void setRecentTwoAccountName(String accountName) { Settings.RECENT_TWO_ACCOUNT_NAME.putValue(accountName); } private static void removeRecentTwoAccountName() { Settings.RECENT_TWO_ACCOUNT_NAME.remove(); } public static Account getRecentTwoAccount() { Account activeAccount = getActiveAccount(); if (activeAccount == null) { return null; } Account recentOneAccount = getRecentOneAccount(); if (recentOneAccount == null) { return null; } String accountName = getRecentTwoAccountName(); if (!TextUtils.isEmpty(accountName) && !TextUtils.equals(accountName, activeAccount.name) && !TextUtils.equals(accountName, recentOneAccount.name)) { Account account = getAccountByName(accountName); if (account != null) { return account; } } for (Account account : getAccounts()) { if (!account.equals(activeAccount) && !account.equals(recentOneAccount)) { setRecentTwoAccountName(account.name); return account; } } removeRecentTwoAccountName(); return null; } // NOTICE: Be sure to check hasAccount() before calling this. // NOTE: // Only intended for selecting an active account when there is none, changing active // account should be handled in settings. public static void selectAccount(Activity activity, Intent onSelectedIntent) { if (getAccounts().length == 0) { throw new IllegalStateException("Should have checked for hasAccount()"); } activity.startActivity(SelectAccountActivity.makeIntent(onSelectedIntent, activity)); } public static boolean ensureActiveAccountAvailability(Activity activity) { boolean accountAvailable = true; if (!hasAccount()) { accountAvailable = false; addAccount(activity, activity.getIntent()); } else if (!hasActiveAccount()) { accountAvailable = false; selectAccount(activity, activity.getIntent()); } if (!accountAvailable) { activity.finish(); } return accountAvailable; } public static String getPassword(Account account) { return getAccountManager().getPassword(account); } public static void setPassword(Account account, String password) { getAccountManager().setPassword(account, password); } public static String peekAuthToken(Account account, String type) { return getAccountManager().peekAuthToken(account, type); } public static void getAuthToken(Account account, String type, AccountManagerCallback<Bundle> callback, Handler handler) { getAccountManager().getAuthToken(account, type, null, true, callback, handler); } public interface GetAuthTokenListener { void onResult(String authToken); void onFailed(); } private static AccountManagerCallback<Bundle> makeGetAuthTokenCallback( final GetAuthTokenListener listener) { return new AccountManagerCallback<Bundle>() { @Override public void run(AccountManagerFuture<Bundle> future) { try { String authToken = future.getResult() .getString(AccountManager.KEY_AUTHTOKEN); if (!TextUtils.isEmpty(authToken)) { listener.onResult(authToken); } else { listener.onFailed(); } } catch (AuthenticatorException | IOException | OperationCanceledException e) { e.printStackTrace(); listener.onFailed(); } } }; } public static void getAuthToken(Account account, String type, GetAuthTokenListener listener, Handler handler) { getAuthToken(account, type, makeGetAuthTokenCallback(listener), handler); } public static void getAuthToken(Account account, String type, GetAuthTokenListener listener) { getAuthToken(account, type, listener, null); } public static void setAuthToken(Account account, String type, String authToken) { getAccountManager().setAuthToken(account, type, authToken); } public static void invalidateAuthToken(String authToken) { getAccountManager().invalidateAuthToken(AccountContract.ACCOUNT_TYPE, authToken); } // User name is different from username: user name is the display name in User.name, but // username is the account name for logging in. public static String getUserName(Account account) { return AccountPreferences.forAccount(account).getString(AccountContract.KEY_USER_NAME, null); } public static String getUserName() { return getUserName(getActiveAccount()); } public static void setUserName(Account account, String userName) { AccountPreferences.forAccount(account).putString(AccountContract.KEY_USER_NAME, userName); } public static long getUserId(Account account) { return AccountPreferences.forAccount(account).getLong(AccountContract.KEY_USER_ID, AccountContract.INVALID_USER_ID); } public static long getUserId() { return getUserId(getActiveAccount()); } public static void setUserId(Account account, long userId) { AccountPreferences.forAccount(account).putLong(AccountContract.KEY_USER_ID, userId); } public static String getRefreshToken(Account account, String authTokenType) { return AccountPreferences.forAccount(account).getString(getRefreshTokenKey(authTokenType), null); } public static void setRefreshToken(Account account, String authTokenType, String refreshToken) { AccountPreferences.forAccount(account).putString(getRefreshTokenKey(authTokenType), refreshToken); } private static String getRefreshTokenKey(String authTokenType) { switch (authTokenType) { case AccountContract.AUTH_TOKEN_TYPE_API_V2: return AccountContract.KEY_REFRESH_TOKEN_API_V2; case AccountContract.AUTH_TOKEN_TYPE_FRODO: return AccountContract.KEY_REFRESH_TOKEN_FRODO; default: throw new IllegalArgumentException("Unknown authTokenType: " + authTokenType); } } public static User getUser(Account account) { String userInfoJson = AccountPreferences.forAccount(account).getString( AccountContract.KEY_USER_INFO, null); if (!TextUtils.isEmpty(userInfoJson)) { try { return GsonHelper.get().fromJson(userInfoJson, new TypeToken<User>() {}.getType()); } catch (JsonParseException e) { e.printStackTrace(); } } return null; } public static void setUser(Account account, User user) { String userInfoJson = GsonHelper.get().toJson(user, new TypeToken<User>() {}.getType()); AccountPreferences.forAccount(account).putString(AccountContract.KEY_USER_INFO, userInfoJson); } public static User getUser() { return getUser(getActiveAccount()); } public static void setUser(User user) { setUser(getActiveAccount(), user); } }