diff options
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | api/test-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/accounts/AbstractAccountAuthenticator.java | 186 | ||||
| -rw-r--r-- | core/java/android/accounts/AccountManager.java | 89 | ||||
| -rw-r--r-- | core/java/android/accounts/IAccountAuthenticator.aidl | 8 | ||||
| -rw-r--r-- | core/java/android/accounts/IAccountManager.aidl | 4 | ||||
| -rw-r--r-- | services/core/java/com/android/server/accounts/AccountManagerService.java | 130 |
8 files changed, 392 insertions, 31 deletions
diff --git a/api/current.txt b/api/current.txt index 14301858f1a2..c0177540f744 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2728,6 +2728,7 @@ package android.accounts { method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String); + method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; @@ -2773,6 +2774,7 @@ package android.accounts { method public void clearPassword(android.accounts.Account); method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); + method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public static android.accounts.AccountManager get(android.content.Context); method public android.accounts.Account[] getAccounts(); method public android.accounts.Account[] getAccountsByType(java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 1c87414b9d28..3dfb316cd846 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2829,6 +2829,7 @@ package android.accounts { method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String); + method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; @@ -2874,6 +2875,7 @@ package android.accounts { method public void clearPassword(android.accounts.Account); method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); + method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public static android.accounts.AccountManager get(android.content.Context); method public android.accounts.Account[] getAccounts(); method public android.accounts.Account[] getAccountsByType(java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index cd6ca865c813..d05dba28c70a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2728,6 +2728,7 @@ package android.accounts { method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String); + method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; @@ -2773,6 +2774,7 @@ package android.accounts { method public void clearPassword(android.accounts.Account); method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); + method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public static android.accounts.AccountManager get(android.content.Context); method public android.accounts.Account[] getAccounts(); method public android.accounts.Account[] getAccountsByType(java.lang.String); diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index 041f591e0ba8..a312e3f5b6a7 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -18,6 +18,7 @@ package android.accounts; import android.os.Bundle; import android.os.RemoteException; +import android.text.TextUtils; import android.os.Binder; import android.os.IBinder; import android.content.pm.PackageManager; @@ -121,24 +122,28 @@ public abstract class AbstractAccountAuthenticator { * This is used in the default implementation of * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}. */ - private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE"; + private static final String KEY_AUTH_TOKEN_TYPE = + "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE"; /** * Bundle key used for the {@link String} array of required features in * session bundle. This is used in the default implementation of * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}. */ - private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES"; + private static final String KEY_REQUIRED_FEATURES = + "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES"; /** * Bundle key used for the {@link Bundle} options in session bundle. This is * used in default implementation of {@link #startAddAccountSession} and * {@link startUpdateCredentialsSession}. */ - private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS"; + private static final String KEY_OPTIONS = + "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS"; /** * Bundle key used for the {@link Account} account in session bundle. This is used * used in default implementation of {@link startUpdateCredentialsSession}. */ - private static final String KEY_ACCOUNT = "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT"; + private static final String KEY_ACCOUNT = + "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT"; private final Context mContext; @@ -418,6 +423,7 @@ public abstract class AbstractAccountAuthenticator { } Log.v(TAG, "startUpdateCredentialsSession: result " + AccountManager.sanitizeResult(result)); + } if (result != null) { response.onResult(result); @@ -425,6 +431,33 @@ public abstract class AbstractAccountAuthenticator { } catch (Exception e) { handleException(response, "startUpdateCredentialsSession", account.toString() + "," + authTokenType, e); + + } + } + + public void finishSession( + IAccountAuthenticatorResponse response, + String accountType, + Bundle sessionBundle) throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "finishSession: accountType " + accountType); + } + checkBinderPermission(); + try { + final Bundle result = AbstractAccountAuthenticator.this.finishSession( + new AccountAuthenticatorResponse(response), accountType, sessionBundle); + if (result != null) { + result.keySet(); // force it to be unparcelled + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result)); + } + if (result != null) { + response.onResult(result); + } + } catch (Exception e) { + handleException(response, "finishSession", accountType, e); + } } } @@ -697,7 +730,13 @@ public abstract class AbstractAccountAuthenticator { /** * Starts the add account session to authenticate user to an account of the - * specified accountType. + * specified accountType. No file I/O should be performed in this call. + * Account should be added to device only when {@link #finishSession} is + * called after this. + * <p> + * Note: when overriding this method, {@link #finishSession} should be + * overridden too. + * </p> * * @param response to send the result back to the AccountManager, will never * be null @@ -722,9 +761,13 @@ public abstract class AbstractAccountAuthenticator { * </ul> * @throws NetworkErrorException if the authenticator could not honor the * request due to a network error + * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) */ - public Bundle startAddAccountSession(final AccountAuthenticatorResponse response, - final String accountType, final String authTokenType, final String[] requiredFeatures, + public Bundle startAddAccountSession( + final AccountAuthenticatorResponse response, + final String accountType, + final String authTokenType, + final String[] requiredFeatures, final Bundle options) throws NetworkErrorException { new Thread(new Runnable() { @@ -744,34 +787,43 @@ public abstract class AbstractAccountAuthenticator { } /** - * Asks user to re-authenticate for an account but defers updating the locally stored - * credentials. + * Asks user to re-authenticate for an account but defers updating the + * locally stored credentials. No file I/O should be performed in this call. + * Local credentials should be updated only when {@link #finishSession} is + * called after this. + * <p> + * Note: when overriding this method, {@link #finishSession} should be + * overridden too. + * </p> * * @param response to send the result back to the AccountManager, will never * be null * @param account the account whose credentials are to be updated, will * never be null * @param authTokenType the type of auth token to retrieve after updating - * the credentials, may be null (TODO) + * the credentials, may be null * @param options a Bundle of authenticator-specific options, may be null * @return a Bundle result or null if the result is to be returned via the * response. The result will contain either: * <ul> * <li>{@link AccountManager#KEY_INTENT}, or - * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for updating the - * locally stored credentials later, and if account is - * re-authenticated, {@link AccountManager#KEY_PASSWORD} and - * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the - * status of the account later, or + * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for + * updating the locally stored credentials later, and if account is + * re-authenticated, optional {@link AccountManager#KEY_PASSWORD} + * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking + * the status of the account later, or * <li>{@link AccountManager#KEY_ERROR_CODE} and * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error * </ul> * @throws NetworkErrorException if the authenticator could not honor the * request due to a network error + * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) */ - public Bundle startUpdateCredentialsSession(final AccountAuthenticatorResponse response, - final Account account, final String authTokenType, final Bundle options) - throws NetworkErrorException { + public Bundle startUpdateCredentialsSession( + final AccountAuthenticatorResponse response, + final Account account, + final String authTokenType, + final Bundle options) throws NetworkErrorException { new Thread(new Runnable() { @Override public void run() { @@ -787,4 +839,102 @@ public abstract class AbstractAccountAuthenticator { }).start(); return null; } + + /** + * Finishes the session started by #startAddAccountSession or + * #startUpdateCredentials by installing the account to device with + * AccountManager, or updating the local credentials. File I/O may be + * performed in this call. + * <p> + * Note: when overriding this method, {@link #startAddAccountSession} and + * {@link #startUpdateCredentialsSession} should be overridden too. + * </p> + * + * @param response to send the result back to the AccountManager, will never + * be null + * @param accountType the type of account to authenticate with, will never + * be null + * @param sessionBundle a bundle of session data created by + * {@link #startAddAccountSession} used for adding account to + * device, or by {@link #startUpdateCredentialsSession} used for + * updating local credentials. + * @return a Bundle result or null if the result is to be returned via the + * response. The result will contain either: + * <ul> + * <li>{@link AccountManager#KEY_INTENT}, or + * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and + * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was + * added or local credentials were updated, or + * <li>{@link AccountManager#KEY_ERROR_CODE} and + * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error + * </ul> + * @throws NetworkErrorException + * @see #startAddAccountSession and #startUpdateCredentialsSession + */ + public Bundle finishSession( + final AccountAuthenticatorResponse response, + final String accountType, + final Bundle sessionBundle) throws NetworkErrorException { + if (TextUtils.isEmpty(accountType)) { + Log.e(TAG, "Account type cannot be empty."); + Bundle result = new Bundle(); + result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); + result.putString(AccountManager.KEY_ERROR_MESSAGE, + "accountType cannot be empty."); + return result; + } + + if (sessionBundle == null) { + Log.e(TAG, "Session bundle cannot be null."); + Bundle result = new Bundle(); + result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); + result.putString(AccountManager.KEY_ERROR_MESSAGE, + "sessionBundle cannot be null."); + return result; + } + + if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) { + // We cannot handle Session bundle not created by default startAddAccountSession(...) + // nor startUpdateCredentialsSession(...) implementation. Return error. + Bundle result = new Bundle(); + result.putInt(AccountManager.KEY_ERROR_CODE, + AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); + result.putString(AccountManager.KEY_ERROR_MESSAGE, + "Authenticator must override finishSession if startAddAccountSession" + + " or startUpdateCredentialsSession is overridden."); + response.onResult(result); + return result; + } + String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE); + Bundle options = sessionBundle.getBundle(KEY_OPTIONS); + String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES); + Account account = sessionBundle.getParcelable(KEY_ACCOUNT); + boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT); + + // Actual options passed to add account or update credentials flow. + Bundle sessionOptions = new Bundle(sessionBundle); + // Remove redundant extras in session bundle before passing it to addAccount(...) or + // updateCredentials(...). + sessionOptions.remove(KEY_AUTH_TOKEN_TYPE); + sessionOptions.remove(KEY_REQUIRED_FEATURES); + sessionOptions.remove(KEY_OPTIONS); + sessionOptions.remove(KEY_ACCOUNT); + + if (options != null) { + // options may contains old system info such as + // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update + // credentials flow, we should replace with the new values of the current call added + // to sessionBundle by AccountManager or AccountManagerService. + options.putAll(sessionOptions); + sessionOptions = options; + } + + // Session bundle created by startUpdateCredentialsSession default implementation should + // contain KEY_ACCOUNT. + if (containsKeyAccount) { + return updateCredentials(response, account, authTokenType, options); + } + // Otherwise, session bundle was created by startAddAccountSession default implementation. + return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions); + } } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 5557905e4f47..ada1ac268fc0 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -2617,7 +2617,8 @@ public class AccountManager { * <p> * <p> * <b>NOTE:</b> The account will not be installed to the device by calling - * this api alone. + * this api alone. #finishSession should be called after this to install the + * account on device. * * @param accountType The type of account to add; must not be null * @param authTokenType The type of auth token (see {@link #getAuthToken}) @@ -2665,6 +2666,7 @@ public class AccountManager { * problem creating a new account, usually because of network * trouble * </ul> + * @see #finishSession */ public AccountManagerFuture<Bundle> startAddAccountSession( final String accountType, @@ -2697,14 +2699,14 @@ public class AccountManager { /** * Asks the user to enter a new password for an account but not updating the - * saved credentials for the account until finishSession is - * called. + * saved credentials for the account until {@link #finishSession} is called. * <p> * This method may be called from any thread, but the returned * {@link AccountManagerFuture} must not be used on the main thread. * <p> * <b>NOTE:</b> The saved credentials for the account alone will not be - * updated by calling this API alone . + * updated by calling this API alone. #finishSession should be called after + * this to update local credentials * * @param account The account to update credentials for * @param authTokenType The credentials entered must allow an auth token of @@ -2723,15 +2725,14 @@ public class AccountManager { * the main thread * @return An {@link AccountManagerFuture} which resolves to a Bundle with * these fields if an activity was supplied and user was - * successfully re-authenticated to the account (TODO: default impl - * only returns escorw?): + * successfully re-authenticated to the account: * <ul> * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for * updating the local credentials on device later. - * <li>{@link #KEY_PASSWORD} - optional, the password or password hash of the - * account - * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check status of - * the account + * <li>{@link #KEY_PASSWORD} - optional, the password or password + * hash of the account + * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check + * status of the account * </ul> * If no activity was specified, the returned Bundle contains * {@link #KEY_INTENT} with the {@link Intent} needed to launch the @@ -2747,6 +2748,7 @@ public class AccountManager { * problem verifying the password, usually because of network * trouble * </ul> + * @see #finishSession */ public AccountManagerFuture<Bundle> startUpdateCredentialsSession( final Account account, @@ -2770,4 +2772,71 @@ public class AccountManager { } }.start(); } + + /** + * Finishes the session started by {@link #startAddAccountSession} or + * {@link #startUpdateCredentialsSession}. This will either add the account + * to AccountManager or update the local credentials stored. + * <p> + * This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * @param sessionBundle a {@link Bundle} created by {@link #startAddAccountSession} or + * {@link #startUpdateCredentialsSession} + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to + * create an account or reauthenticate existing account; used + * only to call startActivity(); if null, the prompt will not + * be launched directly, but the necessary {@link Intent} will + * be returned to the caller instead + * @param callback Callback to invoke when the request completes, null for + * no callback + * @param handler {@link Handler} identifying the callback thread, null for + * the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * these fields if an activity was supplied and an account was added + * to device or local credentials were updated:: + * <ul> + * <li>{@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account + * </ul> + * If no activity was specified and additional information is needed + * from user, the returned Bundle may contains only + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * actual account creation process. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li>{@link AuthenticatorException} if no authenticator was + * registered for this account type or the authenticator failed to + * respond + * <li>{@link OperationCanceledException} if the operation was + * canceled for any reason, including the user canceling the + * creation process or adding accounts (of this type) has been + * disabled by policy + * <li>{@link IOException} if the authenticator experienced an I/O + * problem creating a new account, usually because of network + * trouble + * </ul> + * @see #startAddAccountSession and #startUpdateCredentialsSession + */ + public AccountManagerFuture<Bundle> finishSession( + final Bundle sessionBundle, + final Activity activity, + AccountManagerCallback<Bundle> callback, + Handler handler) { + if (sessionBundle == null) { + throw new IllegalArgumentException("sessionBundle is null"); + } + + /* Add information required by add account flow */ + final Bundle appInfo = new Bundle(); + appInfo.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.finishSession(mResponse, sessionBundle, activity != null, appInfo); + } + }.start(); + } } diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl index 921fb19ddb63..6bda80056b6f 100644 --- a/core/java/android/accounts/IAccountAuthenticator.aidl +++ b/core/java/android/accounts/IAccountAuthenticator.aidl @@ -96,4 +96,12 @@ oneway interface IAccountAuthenticator { */ void startUpdateCredentialsSession(in IAccountAuthenticatorResponse response, in Account account, String authTokenType, in Bundle options); + + /** + * Finishes the session started by startAddAccountSession(...) or + * startUpdateCredentialsSession(...) by adding account to or updating local credentials + * in the IAccountManager. + */ + void finishSession(in IAccountAuthenticatorResponse response, String accountType, + in Bundle sessionBundle); } diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 8489e47c0ba4..4af9f3325dff 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -91,4 +91,8 @@ interface IAccountManager { /* Update credentials in two steps. */ void startUpdateCredentialsSession(in IAccountManagerResponse response, in Account account, String authTokenType, boolean expectActivityLaunch, in Bundle options); + + /* Finish session started by startAddAccountSession(...) or startUpdateCredentialsSession(...) */ + void finishSession(in IAccountManagerResponse response, in Bundle sessionBundle, + boolean expectActivityLaunch, in Bundle appInfo); } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 91702cfac2f1..502d61a057fd 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2475,6 +2475,129 @@ public class AccountManagerService } } + @Override + public void finishSession(IAccountManagerResponse response, + @NonNull Bundle sessionBundle, + boolean expectActivityLaunch, + Bundle appInfo) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "finishSession: response "+ response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) { + throw new IllegalArgumentException("response is null"); + } + + // Session bundle is the encrypted bundle of the original bundle created by authenticator. + // Account type is added to it before encryption. + if (sessionBundle == null || sessionBundle.size() == 0) { + throw new IllegalArgumentException("sessionBundle is empty"); + } + + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId)) { + sendErrorResponse(response, + AccountManager.ERROR_CODE_USER_RESTRICTED, + "User is not allowed to add an account!"); + showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId); + return; + } + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final Bundle decryptedBundle; + final String accountType; + // First decrypt session bundle to get account type for checking permission. + try { + CryptoHelper cryptoHelper = CryptoHelper.getInstance(); + decryptedBundle = cryptoHelper.decryptBundle(sessionBundle); + if (decryptedBundle == null) { + sendErrorResponse( + response, + AccountManager.ERROR_CODE_BAD_REQUEST, + "failed to decrypt session bundle"); + return; + } + accountType = decryptedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE); + // Account type cannot be null. This should not happen if session bundle was created + // properly by #StartAccountSession. + if (TextUtils.isEmpty(accountType)) { + sendErrorResponse( + response, + AccountManager.ERROR_CODE_BAD_ARGUMENTS, + "accountType is empty"); + return; + } + + // If by any chances, decryptedBundle contains colliding keys with + // system info + // such as AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or + // update credentials flow, we should replace with the new values of the current call. + if (appInfo != null) { + decryptedBundle.putAll(appInfo); + } + + // Add info that may be used by add account or update credentials flow. + decryptedBundle.putInt(AccountManager.KEY_CALLER_UID, uid); + decryptedBundle.putInt(AccountManager.KEY_CALLER_PID, pid); + } catch (GeneralSecurityException e) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.v(TAG, "Failed to decrypt session bundle!", e); + } + sendErrorResponse( + response, + AccountManager.ERROR_CODE_BAD_REQUEST, + "failed to decrypt session bundle"); + return; + } + + if (!canUserModifyAccountsForType(userId, accountType)) { + sendErrorResponse( + response, + AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + userId); + return; + } + + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + logRecordWithUid( + accounts, + DebugDbHelper.ACTION_CALLED_ACCOUNT_SESSION_FINISH, + TABLE_ACCOUNTS, + uid); + new Session( + accounts, + response, + accountType, + expectActivityLaunch, + true /* stripAuthTokenFromResult */, + null /* accountName */, + false /* authDetailsRequired */, + true /* updateLastAuthenticationTime */) { + @Override + public void run() throws RemoteException { + mAuthenticator.finishSession(this, mAccountType, decryptedBundle); + } + + @Override + protected String toDebugString(long now) { + return super.toDebugString(now) + + ", finishSession" + + ", accountType " + accountType; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + private void showCantAddAccount(int errorCode, int userId) { Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class); cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode); @@ -3589,10 +3712,11 @@ public class AccountManagerService private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add"; private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove"; - // TODO: This action doesn't add account to accountdb. Account is only - // added in finishAddAccount or finishAddAccountAsUser which may be in - // a different user profile. + //This action doesn't add account to accountdb. Account is only + // added in finishSession which may be in a different user profile. private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add"; + private static String ACTION_CALLED_ACCOUNT_SESSION_FINISH = + "action_called_account_session_finish"; private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |