Merge "AccountManager: add finishSession(...) API."
diff --git a/api/current.txt b/api/current.txt
index 1430185..c017754 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2728,6 +2728,7 @@
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 @@
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 1c87414..3dfb316 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2829,6 +2829,7 @@
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 @@
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 cd6ca86..d05dba2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2728,6 +2728,7 @@
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 @@
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 041f591..a312e3f 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -18,6 +18,7 @@
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 @@
* 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 @@
}
Log.v(TAG, "startUpdateCredentialsSession: result "
+ AccountManager.sanitizeResult(result));
+
}
if (result != null) {
response.onResult(result);
@@ -425,6 +431,33 @@
} 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 @@
/**
* 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 @@
* </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 @@
}
/**
- * 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 @@
}).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 5557905..ada1ac2 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2617,7 +2617,8 @@
* <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 @@
* problem creating a new account, usually because of network
* trouble
* </ul>
+ * @see #finishSession
*/
public AccountManagerFuture<Bundle> startAddAccountSession(
final String accountType,
@@ -2697,14 +2699,14 @@
/**
* 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 @@
* 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 @@
* problem verifying the password, usually because of network
* trouble
* </ul>
+ * @see #finishSession
*/
public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
final Account account,
@@ -2770,4 +2772,71 @@
}
}.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 921fb19..6bda800 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -96,4 +96,12 @@
*/
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 8489e47..4af9f33 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -91,4 +91,8 @@
/* 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 91702cf..502d61a 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 @@
}
}
+ @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 @@
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");