diff --git a/api/current.txt b/api/current.txt index 43e748790050f..a87291f6f778e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2709,7 +2709,9 @@ package android.accounts { method public void invalidateAuthToken(java.lang.String, java.lang.String); method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle); method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String); - method public android.accounts.AccountManagerFuture removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback, android.os.Handler); + method public deprecated android.accounts.AccountManagerFuture removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback, android.os.Handler); + method public android.accounts.AccountManagerFuture removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback, android.os.Handler); + method public boolean removeAccountExplicitly(android.accounts.Account); method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener); method public android.accounts.AccountManagerFuture renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback, android.os.Handler); method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 09b484bab2ca7..64c2fd0e3e114 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -17,37 +17,37 @@ package android.accounts; import android.app.Activity; -import android.content.Intent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; -import android.content.BroadcastReceiver; import android.content.res.Resources; import android.database.SQLException; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.RemoteException; import android.os.Parcelable; -import android.os.Build; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; -import android.util.Log; import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.google.android.collect.Maps; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; -import java.util.HashMap; -import java.util.Map; - -import com.android.internal.R; -import com.google.android.collect.Maps; +import java.util.concurrent.TimeoutException; /** * This class provides access to a centralized registry of the user's @@ -747,13 +747,17 @@ public class AccountManager { * null for the main thread * @return An {@link AccountManagerFuture} which resolves to a Boolean, * true if the account has been successfully removed + * @deprecated use + * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)} + * instead */ + @Deprecated public AccountManagerFuture removeAccount(final Account account, AccountManagerCallback callback, Handler handler) { if (account == null) throw new IllegalArgumentException("account is null"); return new Future2Task(handler, callback) { public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account); + mService.removeAccount(mResponse, account, false); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -764,10 +768,61 @@ public class AccountManager { }.start(); } + /** + * Removes an account from the AccountManager. Does nothing if the account + * does not exist. Does not delete the account from the server. + * The authenticator may have its own policies preventing account + * deletion, in which case the account will not be deleted. + * + *

This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param account The {@link Account} to remove + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to delete an + * account; used only to call startActivity(); if null, the prompt + * will not be launched directly, but the {@link Intent} may 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 + * {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account + * was removed or if active. If no activity was specified, the returned + * Bundle contains only {@link #KEY_INTENT} with the {@link Intent} + * needed to launch the actual account removal process, if authenticator + * needs the activity launch. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: + *

    + *
  • {@link AuthenticatorException} if no authenticator was registered for + * this account type or the authenticator failed to respond + *
  • {@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 + *
+ */ + public AccountManagerFuture removeAccount(final Account account, + final Activity activity, AccountManagerCallback callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.removeAccount(mResponse, account, activity != null); + } + }.start(); + } + /** * @see #removeAccount(Account, AccountManagerCallback, Handler) * @hide + * @deprecated use + * {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)} + * instead */ + @Deprecated public AccountManagerFuture removeAccountAsUser(final Account account, AccountManagerCallback callback, Handler handler, final UserHandle userHandle) { @@ -775,7 +830,7 @@ public class AccountManager { if (userHandle == null) throw new IllegalArgumentException("userHandle is null"); return new Future2Task(handler, callback) { public void doWork() throws RemoteException { - mService.removeAccountAsUser(mResponse, account, userHandle.getIdentifier()); + mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier()); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -786,6 +841,52 @@ public class AccountManager { }.start(); } + /** + * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler) + * @hide + */ + public AccountManagerFuture removeAccountAsUser(final Account account, + final Activity activity, AccountManagerCallback 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 AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.removeAccountAsUser(mResponse, account, activity != null, + userHandle.getIdentifier()); + } + }.start(); + } + + /** + * Removes an account directly. Normally used by authenticators, not + * directly by applications. Does not delete the account from the server. + * The authenticator may have its own policies preventing account deletion, + * in which case the account will not be deleted. + *

+ * It is safe to call this method from the main thread. + *

+ * This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and to have the + * same UID or signature as the account's authenticator. + * + * @param account The {@link Account} to delete. + * @return True if the account was successfully deleted, false if the + * account did not exist, the account is null, or another error + * occurs. + */ + public boolean removeAccountExplicitly(Account account) { + if (account == null) throw new IllegalArgumentException("account is null"); + try { + return mService.removeAccountExplicitly(account); + } catch (RemoteException e) { + // won't ever happen + throw new RuntimeException(e); + } + } + /** * 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 diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index a13378800047b..bc75b9b7fc76b 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -37,8 +37,11 @@ interface IAccountManager { void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features); 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 removeAccount(in IAccountManagerResponse response, in Account account, + boolean expectActivityLaunch); + void removeAccountAsUser(in IAccountManagerResponse response, in Account account, + boolean expectActivityLaunch, int userId); + boolean removeAccountExplicitly(in Account account); void invalidateAuthToken(String accountType, String authToken); String peekAuthToken(in Account account, String authTokenType); void setAuthToken(in Account account, String authTokenType, String authToken); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index d480f68bfe3be..85eed859f6acf 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -77,7 +77,6 @@ import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.FgThread; - import com.google.android.collect.Lists; import com.google.android.collect.Sets; @@ -1043,7 +1042,8 @@ public class AccountManagerService } @Override - public void removeAccount(IAccountManagerResponse response, Account account) { + public void removeAccount(IAccountManagerResponse response, Account account, + boolean expectActivityLaunch) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account + ", response " + response @@ -1088,7 +1088,7 @@ public class AccountManagerService } try { - new RemoveAccountSession(accounts, response, account).bind(); + new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -1096,7 +1096,7 @@ public class AccountManagerService @Override public void removeAccountAsUser(IAccountManagerResponse response, Account account, - int userId) { + boolean expectActivityLaunch, int userId) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account + ", response " + response @@ -1145,7 +1145,30 @@ public class AccountManagerService } try { - new RemoveAccountSession(accounts, response, account).bind(); + new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public boolean removeAccountExplicitly(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccountExplicitly: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) { + return false; + } + long identityToken = clearCallingIdentity(); + try { + return removeAccountInternal(accounts, account); } finally { restoreCallingIdentity(identityToken); } @@ -1154,8 +1177,8 @@ public class AccountManagerService private class RemoveAccountSession extends Session { final Account mAccount; public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, - Account account) { - super(accounts, response, account.type, false /* expectActivityLaunch */, + Account account, boolean expectActivityLaunch) { + super(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */); mAccount = account; } @@ -1203,10 +1226,12 @@ public class AccountManagerService removeAccountInternal(getUserAccountsForCaller(), account); } - private void removeAccountInternal(UserAccounts accounts, Account account) { + private boolean removeAccountInternal(UserAccounts accounts, Account account) { + int deleted; synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); - db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + deleted = db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + + "=?", new String[]{account.name, account.type}); removeAccountFromCacheLocked(accounts, account); sendAccountsChangedBroadcast(accounts.userId); @@ -1226,6 +1251,7 @@ public class AccountManagerService Binder.restoreCallingIdentity(id); } } + return (deleted > 0); } @Override