Ads per-user APIs to manage accounts through the AccountManager

Bug: 16056552
Bug: 14642886
Change-Id: I17ff6c2515285e63c84cecf2f861d10666c393c5
This commit is contained in:
Alexandra Gherghina
2014-07-03 11:40:08 +01:00
parent 3f4f2fbcd0
commit 999d394ade
9 changed files with 271 additions and 25 deletions

View File

@@ -155,6 +155,8 @@ public class AccountManager {
/** @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 @@ public class AccountManager {
* @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) {
@@ -697,6 +698,28 @@ public class AccountManager {
}.start();
}
/**
* @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
@@ -1183,7 +1206,8 @@ public class AccountManager {
* <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>
@@ -1207,6 +1231,30 @@ public class AccountManager {
}.start();
}
/**
* @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
@@ -1608,8 +1656,10 @@ public class AccountManager {
}
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 class AccountManager {
}
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;
}

View File

@@ -84,7 +84,8 @@ public interface AccountManagerFuture<V> {
* 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

View File

@@ -19,6 +19,7 @@ package android.accounts;
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 @@ import com.android.internal.R;
* 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) {

View File

@@ -38,6 +38,7 @@ interface IAccountManager {
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 @@ interface IAccountManager {
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,

View File

@@ -2598,9 +2598,17 @@ public class DevicePolicyManager {
* @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);
}

View File

@@ -146,6 +146,7 @@ interface IDevicePolicyManager {
void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
String[] getAccountTypesWithManagementDisabled();
String[] getAccountTypesWithManagementDisabledAsUser(int userId);
void setLockTaskPackages(in String[] packages);
String[] getLockTaskPackages();

View File

@@ -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" />

View File

@@ -871,17 +871,84 @@ public class AccountManagerService
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:
@@ -1526,20 +1593,23 @@ public class AccountManagerService
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;
}
@@ -1575,6 +1645,92 @@ public class AccountManagerService
}
}
@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,
@@ -2766,18 +2922,18 @@ public class AccountManagerService
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;

View File

@@ -3888,11 +3888,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@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++) {