AccountManager: add startUpdateCredentials API.
Adding startUpdateCredentials API to AccountManager and
AbstractAccountAuthenticator.
Change-Id: Id9a1ff86764f2fde01fd8482594e4ae34e1f3bd1
diff --git a/api/current.txt b/api/current.txt
index e17bf28..2a363cb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2733,6 +2733,7 @@
method public final android.os.IBinder getIBinder();
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
+ method public android.os.Bundle startUpdateCredentialsSession(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
}
@@ -2798,6 +2799,7 @@
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+ method public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
diff --git a/api/system-current.txt b/api/system-current.txt
index d8b9b6f..3b7ffcb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2832,6 +2832,7 @@
method public final android.os.IBinder getIBinder();
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
+ method public android.os.Bundle startUpdateCredentialsSession(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
}
@@ -2897,6 +2898,7 @@
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+ method public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 185ceb4..041f591 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -119,21 +119,26 @@
/**
* Bundle key used for the {@link String} account type in session bundle.
* This is used in the default implementation of
- * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession.
+ * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
*/
private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.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}. TODO: and startUpdateCredentialsSession.
+ * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
*/
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}. TODO:
- * and startUpdateCredentialsSession.
+ * used in default implementation of {@link #startAddAccountSession} and
+ * {@link startUpdateCredentialsSession}.
*/
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 final Context mContext;
@@ -385,6 +390,43 @@
handleException(response, "startAddAccountSession", accountType, e);
}
}
+
+ @Override
+ public void startUpdateCredentialsSession(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String authTokenType,
+ Bundle loginOptions) throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "startUpdateCredentialsSession: "
+ + account
+ + ", authTokenType "
+ + authTokenType);
+ }
+ checkBinderPermission();
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .startUpdateCredentialsSession(
+ new AccountAuthenticatorResponse(response),
+ account,
+ authTokenType,
+ loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startUpdateCredentialsSession: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startUpdateCredentialsSession",
+ account.toString() + "," + authTokenType, e);
+ }
+ }
}
private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -700,4 +742,49 @@
}).start();
return null;
}
+
+ /**
+ * Asks user to re-authenticate for an account but defers updating the locally stored
+ * credentials.
+ *
+ * @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)
+ * @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_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
+ */
+ 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() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putParcelable(KEY_ACCOUNT, account);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 42e5e2a..5557905 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2666,9 +2666,14 @@
* trouble
* </ul>
*/
- public AccountManagerFuture<Bundle> startAddAccountSession(final String accountType,
- final String authTokenType, final String[] requiredFeatures, final Bundle options,
- final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ public AccountManagerFuture<Bundle> startAddAccountSession(
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
+ final Bundle options,
+ final Activity activity,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
if (accountType == null) throw new IllegalArgumentException("accountType is null");
final Bundle optionsIn = new Bundle();
if (options != null) {
@@ -2679,8 +2684,89 @@
return new AmsTask(activity, handler, callback) {
@Override
public void doWork() throws RemoteException {
- mService.startAddAccountSession(mResponse, accountType, authTokenType,
- requiredFeatures, activity != null, optionsIn);
+ mService.startAddAccountSession(
+ mResponse,
+ accountType,
+ authTokenType,
+ requiredFeatures,
+ activity != null,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to enter a new password for an account but not updating the
+ * saved credentials for the account until 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 .
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token of
+ * this type to be created (but no actual auth token is
+ * returned); may be null
+ * @param options Authenticator-specific options for the request; may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter
+ * a password; 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 user was
+ * successfully re-authenticated to the account (TODO: default impl
+ * only returns escorw?):
+ * <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
+ * </ul>
+ * If no activity was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * password prompt
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem verifying the password, usually because of network
+ * trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
+ final Account account,
+ final String authTokenType,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startUpdateCredentialsSession(
+ mResponse,
+ account,
+ authTokenType,
+ activity != null,
+ options);
}
}.start();
}
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index b326070..921fb19 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -90,4 +90,10 @@
*/
void startAddAccountSession(in IAccountAuthenticatorResponse response, String accountType,
String authTokenType, in String[] requiredFeatures, in Bundle options);
+
+ /**
+ * Prompts the user for a new password but does not write it to the IAccountManager.
+ */
+ void startUpdateCredentialsSession(in IAccountAuthenticatorResponse response, in Account account,
+ String authTokenType, in Bundle options);
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 5de311e..8489e47 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -88,4 +88,7 @@
String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
in Bundle options);
+ /* Update credentials in two steps. */
+ void startUpdateCredentialsSession(in IAccountManagerResponse response, in Account account,
+ String authTokenType, boolean expectActivityLaunch, in Bundle options);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 37d47c3..93eaf0e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2286,8 +2286,11 @@
}
@Override
- public void startAddAccountSession(final IAccountManagerResponse response, final String accountType,
- final String authTokenType, final String[] requiredFeatures,
+ public void startAddAccountSession(
+ final IAccountManagerResponse response,
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
final boolean expectActivityLaunch,
final Bundle optionsIn) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2571,6 +2574,60 @@
}
@Override
+ public void startUpdateCredentialsSession(
+ IAccountManagerResponse response,
+ final Account account,
+ final String authTokenType,
+ final boolean expectActivityLaunch,
+ final Bundle loginOptions) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "startUpdateCredentialsSession: " + account + ", response " + response
+ + ", authTokenType " + authTokenType + ", expectActivityLaunch "
+ + expectActivityLaunch + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) {
+ throw new IllegalArgumentException("response is null");
+ }
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+ int userId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ new StartAccountSession(
+ accounts,
+ response,
+ account.type,
+ expectActivityLaunch,
+ account.name,
+ false /* authDetailsRequired */,
+ true /* updateLastCredentialTime */) {
+ @Override
+ public void run() throws RemoteException {
+ mAuthenticator.startUpdateCredentialsSession(this, account, authTokenType,
+ loginOptions);
+ }
+
+ @Override
+ protected String toDebugString(long now) {
+ if (loginOptions != null)
+ loginOptions.keySet();
+ return super.toDebugString(now)
+ + ", startUpdateCredentialsSession"
+ + ", " + account
+ + ", authTokenType " + authTokenType
+ + ", loginOptions " + loginOptions;
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
public void editProperties(IAccountManagerResponse response, final String accountType,
final boolean expectActivityLaunch) {
final int callingUid = Binder.getCallingUid();