Ads per-user APIs to manage accounts through the AccountManager
Bug: 16056552
Bug: 14642886
Change-Id: I17ff6c2515285e63c84cecf2f861d10666c393c5
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 806a55b..aab6e80 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -155,6 +155,8 @@
/** @hide */
public static final int ERROR_CODE_USER_RESTRICTED = 100;
+ /** @hide */
+ public static final int ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE = 101;
/**
* Bundle key used for the {@link String} account name in results
@@ -678,8 +680,7 @@
* @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 account has been successfully removed,
- * false if the authenticator forbids deleting this account.
+ * true if the account has been successfully removed
*/
public AccountManagerFuture<Boolean> removeAccount(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler) {
@@ -698,6 +699,28 @@
}
/**
+ * @see #removeAccount(Account, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, userHandle.getIdentifier());
+ }
+ 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();
+ }
+
+ /**
* Removes an auth token from the AccountManager's cache. Does nothing if
* the auth token is not currently in the cache. Applications must call this
* method when the auth token is found to have expired or otherwise become
@@ -1183,7 +1206,8 @@
* <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
+ * 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>
@@ -1208,6 +1232,30 @@
}
/**
+ * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions, final Activity activity,
+ AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.addAccountAsUser(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
* Adds a shared account from the primary user to a secondary user. Adding the shared account
* doesn't take effect immediately. When the target user starts up, any pending shared accounts
* are attempted to be copied to the target user from the primary via calls to the
@@ -1608,8 +1656,10 @@
}
public void onError(int code, String message) {
- if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
- // the authenticator indicated that this request was canceled, do so now
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
cancel(true /* mayInterruptIfRunning */);
return;
}
@@ -1668,7 +1718,10 @@
}
public void onError(int code, String message) {
- if (code == ERROR_CODE_CANCELED) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
cancel(true /* mayInterruptIfRunning */);
return;
}
diff --git a/core/java/android/accounts/AccountManagerFuture.java b/core/java/android/accounts/AccountManagerFuture.java
index af00a08..77670d9 100644
--- a/core/java/android/accounts/AccountManagerFuture.java
+++ b/core/java/android/accounts/AccountManagerFuture.java
@@ -84,7 +84,8 @@
* will be thrown rather than the call returning normally.
* @return the actual result
* @throws android.accounts.OperationCanceledException if the request was canceled for any
- * reason
+ * reason (including if it is forbidden
+ * by policy to modify an account (of that type))
* @throws android.accounts.AuthenticatorException if there was an error communicating with
* the authenticator or if the authenticator returned an invalid response
* @throws java.io.IOException if the authenticator returned an error response that indicates
diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java
index e1717a6..4ac2beb 100644
--- a/core/java/android/accounts/CantAddAccountActivity.java
+++ b/core/java/android/accounts/CantAddAccountActivity.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
+import android.widget.TextView;
import com.android.internal.R;
@@ -27,11 +28,26 @@
* Just shows an error message about the account restrictions for the limited user.
*/
public class CantAddAccountActivity extends Activity {
+ public static final String EXTRA_ERROR_CODE = "android.accounts.extra.ERROR_CODE";
+ public static final int MISSING = -1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_not_authorized);
+
+ int errorCode = getIntent().getIntExtra(EXTRA_ERROR_CODE, MISSING);
+ if (errorCode != MISSING) {
+ TextView errorText = (TextView) findViewById(R.id.description);
+ switch (errorCode) {
+ case AccountManager.ERROR_CODE_USER_RESTRICTED:
+ errorText.setText(R.string.app_no_restricted_accounts);
+ break;
+ default:
+ // TODO: Get better message. See: http://b/14642886
+ errorText.setText(R.string.error_message_title);
+ }
+ }
}
public void onCancelButtonClicked(View view) {
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 1373dc8..a04875d 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -38,6 +38,7 @@
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
void removeAccount(in IAccountManagerResponse response, in Account account);
+ void removeAccountAsUser(in IAccountManagerResponse response, in Account account, int userId);
void invalidateAuthToken(String accountType, String authToken);
String peekAuthToken(in Account account, String authTokenType);
void setAuthToken(in Account account, String authTokenType, String authToken);
@@ -52,6 +53,9 @@
void addAccount(in IAccountManagerResponse response, String accountType,
String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
in Bundle options);
+ void addAccountAsUser(in IAccountManagerResponse response, String accountType,
+ String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+ in Bundle options, int userId);
void updateCredentials(in IAccountManagerResponse response, in Account account,
String authTokenType, boolean expectActivityLaunch, in Bundle options);
void editProperties(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4e7dac0..7a7fdec 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2598,9 +2598,17 @@
* @see #setAccountManagementDisabled
*/
public String[] getAccountTypesWithManagementDisabled() {
+ return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * @see #getAccountTypesWithManagementDisabled()
+ * @hide
+ */
+ public String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
if (mService != null) {
try {
- return mService.getAccountTypesWithManagementDisabled();
+ return mService.getAccountTypesWithManagementDisabledAsUser(userId);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d36497e..54d1eed 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -146,6 +146,7 @@
void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
String[] getAccountTypesWithManagementDisabled();
+ String[] getAccountTypesWithManagementDisabledAsUser(int userId);
void setLockTaskPackages(in String[] packages);
String[] getLockTaskPackages();
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 08d354c..aaadc16 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -876,6 +876,7 @@
<java-symbol type="string" name="config_customResolverActivity" />
<java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="string" name="error_message_title" />
+ <java-symbol type="string" name="app_no_restricted_accounts" />
<java-symbol type="string" name="action_bar_home_description_format" />
<java-symbol type="string" name="action_bar_home_subtitle_description_format" />
<java-symbol type="string" name="wireless_display_route_description" />
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e152ebe..36d67ee 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -871,18 +871,85 @@
checkManageAccountsPermission();
UserHandle user = Binder.getCallingUserHandle();
UserAccounts accounts = getUserAccountsForCaller();
- if (!canUserModifyAccounts(Binder.getCallingUid(), account.type)) {
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!canUserModifyAccounts(userId)) {
try {
+ // TODO: This should be ERROR_CODE_USER_RESTRICTED instead. See http://b/16322768
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"User cannot modify accounts");
} catch (RemoteException re) {
}
return;
}
+ if (!canUserModifyAccountsForType(userId, account.type)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ "User cannot modify accounts of this type (policy).");
+ } catch (RemoteException re) {
+ }
+ return;
+ }
long identityToken = clearCallingIdentity();
cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
+ synchronized (accounts.credentialsPermissionNotificationIds) {
+ for (Pair<Pair<Account, String>, Integer> pair:
+ accounts.credentialsPermissionNotificationIds.keySet()) {
+ if (account.equals(pair.first.first)) {
+ int id = accounts.credentialsPermissionNotificationIds.get(pair);
+ cancelNotification(id, user);
+ }
+ }
+ }
+
+ try {
+ new RemoveAccountSession(accounts, response, account).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void removeAccountAsUser(IAccountManagerResponse response, Account account,
+ int userId) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "removeAccount: " + account
+ + ", response " + response
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid()
+ + ", for user id " + userId);
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+
+ // Only allow the system process to modify accounts of other users
+ enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ + " trying to remove account for " + userId);
+ checkManageAccountsPermission();
+
+ UserAccounts accounts = getUserAccounts(userId);
+ if (!canUserModifyAccounts(userId)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+ "User cannot modify accounts");
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+ if (!canUserModifyAccountsForType(userId, account.type)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ "User cannot modify accounts of this type (policy).");
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+
+ UserHandle user = new UserHandle(userId);
+ long identityToken = clearCallingIdentity();
+
+ cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
synchronized(accounts.credentialsPermissionNotificationIds) {
for (Pair<Pair<Account, String>, Integer> pair:
accounts.credentialsPermissionNotificationIds.keySet()) {
@@ -1526,20 +1593,23 @@
checkManageAccountsPermission();
// Is user disallowed from modifying accounts?
- if (!canUserModifyAccounts(Binder.getCallingUid(), accountType)) {
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!canUserModifyAccounts(userId)) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
"User is not allowed to add an account!");
} catch (RemoteException re) {
}
- Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
- cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- long identityToken = clearCallingIdentity();
+ showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED);
+ return;
+ }
+ if (!canUserModifyAccountsForType(userId, accountType)) {
try {
- mContext.startActivityAsUser(cantAddAccount, UserHandle.CURRENT);
- } finally {
- restoreCallingIdentity(identityToken);
+ response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ "User cannot modify accounts of this type (policy).");
+ } catch (RemoteException re) {
}
+ showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
return;
}
@@ -1576,6 +1646,92 @@
}
@Override
+ public void addAccountAsUser(final IAccountManagerResponse response, final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final boolean expectActivityLaunch, final Bundle optionsIn, int userId) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount: accountType " + accountType
+ + ", response " + response
+ + ", authTokenType " + authTokenType
+ + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid()
+ + ", for user id " + userId);
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ checkManageAccountsPermission();
+
+ // Only allow the system process to add accounts of other users
+ enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ + " trying to add account for " + userId);
+
+ // Is user disallowed from modifying accounts?
+ if (!canUserModifyAccounts(userId)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+ "User is not allowed to add an account!");
+ } catch (RemoteException re) {
+ }
+ showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED);
+ return;
+ }
+ if (!canUserModifyAccountsForType(userId, accountType)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ "User cannot modify accounts of this type (policy).");
+ } catch (RemoteException re) {
+ }
+ showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
+ return;
+ }
+
+ UserAccounts accounts = getUserAccounts(userId);
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+ options.putInt(AccountManager.KEY_CALLER_UID, uid);
+ options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
+ @Override
+ public void run() throws RemoteException {
+ mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
+ options);
+ }
+
+ @Override
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", addAccount"
+ + ", accountType " + accountType
+ + ", requiredFeatures "
+ + (requiredFeatures != null
+ ? TextUtils.join(",", requiredFeatures)
+ : null);
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void showCantAddAccount(int errorCode) {
+ Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
+ cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode);
+ cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ long identityToken = clearCallingIdentity();
+ try {
+ mContext.startActivity(cantAddAccount);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
public void confirmCredentialsAsUser(IAccountManagerResponse response,
final Account account, final Bundle options, final boolean expectActivityLaunch,
int userId) {
@@ -2766,18 +2922,18 @@
Manifest.permission.USE_CREDENTIALS);
}
- private boolean canUserModifyAccounts(int callingUid, String accountType) {
- if (callingUid != Process.myUid()) {
- if (getUserManager().getUserRestrictions(
- new UserHandle(UserHandle.getUserId(callingUid)))
- .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
- return false;
- }
+ private boolean canUserModifyAccounts(int userId) {
+ if (getUserManager().getUserRestrictions(new UserHandle(userId))
+ .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+ return false;
}
+ return true;
+ }
+ private boolean canUserModifyAccountsForType(int userId, String accountType) {
DevicePolicyManager dpm = (DevicePolicyManager) mContext
.getSystemService(Context.DEVICE_POLICY_SERVICE);
- String[] typesArray = dpm.getAccountTypesWithManagementDisabled();
+ String[] typesArray = dpm.getAccountTypesWithManagementDisabledAsUser(userId);
for (String forbiddenType : typesArray) {
if (forbiddenType.equals(accountType)) {
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9e4d90d..e40c812 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3888,11 +3888,17 @@
@Override
public String[] getAccountTypesWithManagementDisabled() {
+ return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId());
+ }
+
+ @Override
+ public String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ enforceCrossUserPermission(userId);
if (!mHasFeature) {
return null;
}
synchronized (this) {
- DevicePolicyData policy = getUserData(UserHandle.getCallingUserId());
+ DevicePolicyData policy = getUserData(userId);
final int N = policy.mAdminList.size();
HashSet<String> resultSet = new HashSet<String>();
for (int i = 0; i < N; i++) {