AccountManager: add isCredentialsUpdateSuggested API.
Change-Id: I27e0db0345f3431b796a944740dab767b45f7871
diff --git a/api/system-current.txt b/api/system-current.txt
index 56323b6..1cd88a7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2860,6 +2860,7 @@
method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
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 isCredentialsUpdateSuggested(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;
@@ -2915,6 +2916,7 @@
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
+ method public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public static deprecated android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.List<android.accounts.Account>, java.lang.String[], java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public boolean notifyAccountAuthenticated(android.accounts.Account);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 690e674..4dca8e2 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -16,16 +16,16 @@
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;
-import android.content.Context;
-import android.content.Intent;
import android.Manifest;
import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
@@ -436,6 +436,7 @@
}
}
+ @Override
public void finishSession(
IAccountAuthenticatorResponse response,
String accountType,
@@ -461,6 +462,24 @@
}
}
+
+ @Override
+ public void isCredentialsUpdateSuggested(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws RemoteException {
+ checkBinderPermission();
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .isCredentialsUpdateSuggested(
+ new AccountAuthenticatorResponse(response), account, statusToken);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "isCredentialsUpdateSuggested", account.toString(), e);
+ }
+ }
}
private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -873,7 +892,8 @@
* <li>{@link AccountManager#KEY_ERROR_CODE} and
* {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
* </ul>
- * @throws NetworkErrorException
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
* @see #startAddAccountSession and #startUpdateCredentialsSession
* @hide
*/
@@ -944,4 +964,32 @@
// Otherwise, session bundle was created by startAddAccountSession default implementation.
return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions);
}
+
+ /**
+ * Checks if update of the account credentials is suggested.
+ *
+ * @param response to send the result back to the AccountManager, will never be null.
+ * @param account the account to check, will never be null
+ * @param statusToken a String of token to check if update of credentials is suggested.
+ * @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_BOOLEAN_RESULT}, true if update of account's
+ * credentials is suggested, false otherwise
+ * <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
+ * @hide
+ */
+ @SystemApi
+ public Bundle isCredentialsUpdateSuggested(
+ final AccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ return result;
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 10f5d0d..259f0da 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -16,6 +16,8 @@
package android.accounts;
+import static android.Manifest.permission.GET_ACCOUNTS;
+
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.Size;
@@ -54,8 +56,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static android.Manifest.permission.GET_ACCOUNTS;
-
/**
* This class provides access to a centralized registry of the user's
* online accounts. The user enters credentials (username and password) once
@@ -2866,4 +2866,50 @@
}
}.start();
}
+
+ /**
+ * Checks whether {@link #updateCredentials} or {@link #startUpdateCredentialsSession} should be
+ * called with respect to the specified account.
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * @param account The {@link Account} to be checked whether {@link #updateCredentials} or
+ * {@link #startUpdateCredentialsSession} should be called
+ * @param statusToken a String of token to check account staus
+ * @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 Boolean, true if the credentials
+ * of the account should be updated.
+ * @hide
+ */
+ @SystemApi
+ public AccountManagerFuture<Boolean> isCredentialsUpdateSuggested(
+ final Account account,
+ final String statusToken,
+ AccountManagerCallback<Boolean> callback,
+ Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+
+ if (TextUtils.isEmpty(statusToken)) {
+ throw new IllegalArgumentException("status token is empty");
+ }
+
+ return new Future2Task<Boolean>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.isCredentialsUpdateSuggested(
+ mResponse,
+ account,
+ statusToken);
+ }
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
}
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 6bda800..8b98ca2 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -104,4 +104,10 @@
*/
void finishSession(in IAccountAuthenticatorResponse response, String accountType,
in Bundle sessionBundle);
+
+ /**
+ * Checks if the credentials of the provided account should be updated.
+ */
+ void isCredentialsUpdateSuggested(in IAccountAuthenticatorResponse response, in Account account,
+ String statusToken);
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 608501f..39dedf4 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -98,4 +98,8 @@
/* Check if an account exists on any user on the device. */
boolean someUserHasAccount(in Account account);
+
+ /* Check if credentials update is suggested */
+ void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account,
+ String statusToken);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0f12818..b86b3fd 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -176,6 +176,7 @@
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
private static final Intent ACCOUNTS_CHANGED_INTENT;
+
static {
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -2491,7 +2492,7 @@
Bundle appInfo) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
- "finishSession: response "+ response
+ "finishSession: response " + response
+ ", expectActivityLaunch " + expectActivityLaunch
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
@@ -2778,6 +2779,99 @@
}
@Override
+ public void isCredentialsUpdateSuggested(
+ IAccountManagerResponse response,
+ final Account account,
+ final String statusToken) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "isCredentialsUpdateSuggested: " + account + ", response " + response
+ + ", 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");
+ }
+ if (TextUtils.isEmpty(statusToken)) {
+ throw new IllegalArgumentException("status token is empty");
+ }
+
+ int uid = Binder.getCallingUid();
+ // Only allow system to start session
+ if (!isSystemUid(uid)) {
+ String msg = String.format(
+ "uid %s cannot stat add account session.",
+ uid);
+ throw new SecurityException(msg);
+ }
+
+ int usrId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(usrId);
+ new Session(accounts, response, account.type, false /* expectActivityLaunch */,
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
+ @Override
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", isCredentialsUpdateSuggested"
+ + ", " + account;
+ }
+
+ @Override
+ public void run() throws RemoteException {
+ mAuthenticator.isCredentialsUpdateSuggested(this, account, statusToken);
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response == null) {
+ return;
+ }
+
+ if (result == null) {
+ sendErrorResponse(
+ response,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle");
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ + response);
+ }
+ // Check to see if an error occurred. We know if an error occurred because all
+ // error codes are greater than 0.
+ if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0)) {
+ sendErrorResponse(response,
+ result.getInt(AccountManager.KEY_ERROR_CODE),
+ result.getString(AccountManager.KEY_ERROR_MESSAGE));
+ return;
+ }
+ if (!result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)) {
+ sendErrorResponse(
+ response,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "no result in response");
+ return;
+ }
+ final Bundle newResult = new Bundle();
+ newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+ result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+ sendResponse(response, newResult);
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
public void editProperties(IAccountManagerResponse response, final String accountType,
final boolean expectActivityLaunch) {
final int callingUid = Binder.getCallingUid();