// Copyright (C) 2016 DigiGene, (www.DigiGene.com)(alinhayati[at]gmail[dot]com) // // 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.digigene.accountauthenticator; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.widget.Toast; import com.digigene.accountauthenticator.activity.RegistrationActivity; import com.digigene.accountauthenticator.result.SignInResult; import com.digigene.accountauthenticator.result.SignUpResult; import java.io.IOException; public class AuthenticatorManager { public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; public static final String KEY_AUTH_TOKEN_TYPE = "authenticatorType"; public static final String KEY_AUTH_ACCOUNT_OPTIONS = "accountOptions"; public static final String KEY_REGISTRATION_ACTIVITY_CLASS_NAME = "registrationActivityClassName"; public static final String KEY_INTERFACE_IMPLEMENTATION_CLASS_NAME = "interfaceImplementationClassName"; public static final String KEY_IS_ADD_FROM_INSIDE_APP = "isFromGetAuthToken"; public static final String KEY_IS_ADDING_NEW_ACCOUNT = "isAddingNewAccount"; public static final String CALLBACK_THREAD_NAME = "callBackThreadName"; private boolean isCallbackRunInBackgroundThread = false; // private boolean isRunInBackground = false; // private boolean isNotificationShownInBackground = false; public static AuthenticatorManager authenticatorManager; private AbstractInterfaceImplementation interfaceImplementation; private Activity activity; private Context context; private String[] authTokenTypes; private String accountType; public AuthenticatorManager(@NonNull Context context, @NonNull String accountType, @NonNull Activity callingActivity, @NonNull Class<? extends RegistrationActivity> registrationActivityClass, @NonNull Class<? extends AbstractInterfaceImplementation> interfaceImplementationClass) { if (callingActivity == null || registrationActivityClass == null || interfaceImplementationClass == null) { throw new IllegalArgumentException("None of the arguments in Authenticator Manager shall be null"); } this.activity = callingActivity; this.context = context; this.accountType = accountType; try { this.interfaceImplementation = interfaceImplementationClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } this.authTokenTypes = interfaceImplementation.userAccessTypes(); this.isCallbackRunInBackgroundThread = interfaceImplementation.setDoesCallbackRunInBackgroundThread(); // this.isRunInBackground = interfaceImplementation.setIsRunInBackground(); // this.isNotificationShownInBackground = interfaceImplementation.setIsNotificationShownInBackground(); if (authTokenTypes == null) { throw new RuntimeException("Authentication token types cannot be null"); } saveClassNamesInSharedPref(registrationActivityClass.getName(), interfaceImplementationClass.getName()); } public void getAccessToken(String accountName, String authTokenType, Bundle options) { if (accountName == null || accountName.trim().isEmpty()) { Toast.makeText(context, context.getString(R.string.auth_msg_account_name_is_null), Toast.LENGTH_SHORT).show(); return; } Account account; if (options == null) { options = new Bundle(); } Handler handler = null; if (authTokenType == null) { authTokenType = authTokenTypes[0]; } else { if (!isAuthTokenValid(authTokenType, authTokenTypes)) throw new IllegalArgumentException("Authentication token type is not valid."); } if (isCallbackRunInBackgroundThread) handler = setHandler(CALLBACK_THREAD_NAME); account = new Account(accountName, accountType); AccountManagerCallback accountManagerCallback = null; boolean isAddingNewAccount = options.getBoolean(AuthenticatorManager.KEY_IS_ADDING_NEW_ACCOUNT, false); if (!isAddingNewAccount) { accountManagerCallback = getAccessTokenCallBack(authTokenType, options, account); } getAccessTokenFromAccountManager(account, authTokenType, options, accountManagerCallback, handler); } public void addAccount(String authTokenType, String[] requiredFeatures, Bundle options) { AccountManager accountManager = AccountManager.get(context); Handler handler = null; if (options == null) { options = new Bundle(); } if (isCallbackRunInBackgroundThread) handler = setHandler(CALLBACK_THREAD_NAME); options.putBoolean(AuthenticatorManager.KEY_IS_ADD_FROM_INSIDE_APP, true); options.putBoolean(AuthenticatorManager.KEY_IS_ADDING_NEW_ACCOUNT, true); accountManager.addAccount(accountType, authTokenType, requiredFeatures, options, activity, getAddAccountCallBack(), handler); } private void getAccessTokenFromAccountManager(Account account, String authTokenType, Bundle options, AccountManagerCallback accountManagerCallback, Handler handler) { AccountManager accountManager = AccountManager.get(context); accountManager.getAuthToken(account, authTokenType, options, activity, accountManagerCallback, handler); } private AccountManagerCallback getAccessTokenCallBack(final String authTokenType, final Bundle options, final Account account) { AccountManagerCallback accountManagerCallback = new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { try { Bundle bundle = (Bundle) future.getResult(); String accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME, null); String refreshToken = bundle.getString(AccountManager.KEY_PASSWORD, null); String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN, null); if (authToken != null) { signInAndDoAfter(account, authTokenType, authToken, options); } else { if (refreshToken != null) { signUpAndDoAfter(account, authTokenType, refreshToken, options); } else { if (accountName != null) { addAccount(authTokenType, null, options); } else { Toast.makeText(context, context.getString(R.string.auth_msg_account_does_not_exist), Toast.LENGTH_SHORT).show(); } } } } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }; return accountManagerCallback; } private AccountManagerCallback getAddAccountCallBack() { AccountManagerCallback accountManagerCallback = new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { try { Bundle result = (Bundle) future.getResult(); String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); String refreshToken = result.getString(AccountManager.KEY_PASSWORD); String authTokenType = result.getString(KEY_AUTH_TOKEN_TYPE); Bundle options = result.getBundle(KEY_AUTH_ACCOUNT_OPTIONS); Account account = new Account(accountName, accountType); signUpAndDoAfter(account, authTokenType, refreshToken, options); } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } Toast.makeText(context, "After registering the user", Toast.LENGTH_SHORT).show(); } }; return accountManagerCallback; } private Handler setHandler(String threadName) { Handler handler; HandlerThread handlerThread = new HandlerThread(threadName); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); return handler; } private boolean isAuthTokenValid(String tokenType, String[] authTokenTypes) { boolean authTokenIsValid = false; for (int i = 0; i < authTokenTypes.length; i++) { if (tokenType.equalsIgnoreCase(authTokenTypes[i])) { authTokenIsValid = true; break; } } return authTokenIsValid; } private Bundle doAfterSignUpIsSuccessful(AccountManager accountManager, Account account, String authTokenType, SignUpResult signUpResult, Bundle options) { Bundle result; if (signUpResult.isSuccessful) { accountManager.setPassword(account, signUpResult.refreshToken); accountManager.setAuthToken(account, authTokenType, signUpResult.accessToken); result = makeResultBundle(account, signUpResult.refreshToken, signUpResult.accessToken); Toast.makeText(context, "New refresh token is acquired from the server", Toast.LENGTH_SHORT).show(); signInAndDoAfter(account, authTokenType, signUpResult.accessToken, options); return result; } else { throw new RuntimeException("Sign-up is not successful due to the following:/n" + signUpResult.errMessage); } } public Bundle getAccessTokenFromCache(Account account, String authTokenType, AccountManager accountManager) { Bundle result; String cachedAuthToken = accountManager.peekAuthToken(account, authTokenType); String refreshToken = accountManager.getPassword(account); if (cachedAuthToken != null) { result = makeResultBundle(account, refreshToken, cachedAuthToken); return result; } return null; } public Bundle makeResultBundle(Account account, String refreshToken, String accessToken) { Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_PASSWORD, refreshToken); result.putString(AccountManager.KEY_AUTHTOKEN, accessToken); return result; } private SignInResult signInAndDoAfter(final Account account, final String authTokenType, final String authToken, final Bundle options) { final SignInResult[] signInResult = new SignInResult[1]; new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { signInResult[0] = interfaceImplementation.signInToServer(context, account, authTokenType, authToken, options); return null; } @Override protected void onPostExecute(Void aVoid) { if (signInResult[0].isSuccessful) { interfaceImplementation.doAfterSignInIsSuccessful(context, account, authTokenType, authToken, signInResult[0], options); } else { if (signInResult[0].isAccessTokenExpired) { AccountManager accountManager = AccountManager.get(context); accountManager.invalidateAuthToken(accountType, authToken); Toast.makeText(context, signInResult[0].errMessage + ", getting new access token from the server", Toast.LENGTH_SHORT).show(); signUpAndDoAfter(account, authTokenType, accountManager.getPassword(account), options); } else { Toast.makeText(context, "Sign-in was not possible due to the following:\n" + signInResult[0].errMessage, Toast.LENGTH_SHORT).show(); } } } }.execute(); return signInResult[0]; } public Bundle signUpAndDoAfter(final Account account, final String authTokenType, final String refreshToken, final Bundle options) { final Bundle[] result = new Bundle[1]; final SignUpResult[] signUpResult = new SignUpResult[1]; if (refreshToken != null) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { signUpResult[0] = interfaceImplementation.signUpToServer(context, account, authTokenType, refreshToken, options); return null; } @Override protected void onPostExecute(Void aVoid) { if (signUpResult[0].isSuccessful) { result[0] = doAfterSignUpIsSuccessful(AccountManager.get(context), account, authTokenType, signUpResult[0], options); } else { AccountManager accountManager = AccountManager.get(context); accountManager.clearPassword(account); interfaceImplementation.doAfterSignUpIsUnsuccessful(context, account, authTokenType, signUpResult[0], options); } } }.execute(); } else { throw new RuntimeException("Refresh token is null in authenticator"); } return result[0]; } private void saveClassNamesInSharedPref(String registrationActivityName, String interfaceImplementationClassName) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(AuthenticatorManager.KEY_REGISTRATION_ACTIVITY_CLASS_NAME, registrationActivityName); editor.putString(AuthenticatorManager.KEY_INTERFACE_IMPLEMENTATION_CLASS_NAME, interfaceImplementationClassName); editor.commit(); } }