关于使用AccountManager的remove删除Android帐号的细节

Android系统中可以使用AccountManager服务进行帐号的管理(添加,删除,以及其他属性的设置和访问),本文档主要讨论调用remove删除帐号的情况.AccountManager系统服务中有2个接口可以删除帐号,分别是

public boolean removeAccountExplicitly(Account account)

public AccountManagerFuture<Bundle> removeAccount(final Account account,
            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler)

这两个接口的区别是:

  • removeAccountExplicitly是一个同步调用,将直接从AccountSQLite数据库中删除该帐号.
    removeAccount是一个异步调用. 该函数有2种使用方式获得操作结果,第一种方式是使用输入参数指定的回调callback,通过设置输入参数handler, 可以指定该回调函数的执行线程, 如果handler设置为null, 则在主线程执行该回调. 如果回调函数输入为null, 删除操作完成时,不执行回调操作.第二种获得操作结果的方式是通过返回值,因为removeAccount内部是异步操作,所以该函数的返回值使用了Future模式,调用其get方法获得删除操作结果,如果删除操作没有完成,get方法将阻塞.
  • removeAccountExplicitly删除帐号时,不会检查自定义的Authenticator类中的getAccountRemovalAllowed方法是否允许删除该帐号.
    removeAccount则相反, 会做这个检查. 因此,在我们自己实现的Authenticator类中, 可以覆写getAccountRemovalAllowed方法,就可以决定removeAccount是否可以删除该帐号.

需要注意的是,在手机的设置里面手动删除帐号时,也会检查帐号关联的自定义Authenticator类的getAccountRemovalAllowed方法,如果该方法不允许删除该帐号,则手动删除失败.

下面主要讨论removeAccount的一些实现细节.该函数的实现为(AccountManager.java):

public AccountManagerFuture<Bundle> removeAccount(final Account account,
            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
        if (account == null) throw new IllegalArgumentException("account is null");
        return new AmsTask(activity, handler, callback) {
            @Override
            public void doWork() throws RemoteException {
                mService.removeAccount(mResponse, account, activity != null);
            }
        }.start();
    }

该函数创建了一个匿名的AmsTask类对象,然后调用其start方法.其中类AmsTask的实现为(AccountManager.java):

private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
    final IAccountManagerResponse mResponse;
    final Handler mHandler;
    final AccountManagerCallback<Bundle> mCallback;
    final Activity mActivity;
    public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
        super(new Callable<Bundle>() {
            @Override
            public Bundle call() throws Exception {
                throw new IllegalStateException("this should never be called");
            }
        });

        mHandler = handler;
        mCallback = callback;
        mActivity = activity;
        mResponse = new Response();
    }

    public final AccountManagerFuture<Bundle> start() {
        try {
            doWork();
        } catch (RemoteException e) {
            setException(e);
        }
        return this;
    }

    @Override
    protected void set(Bundle bundle) {
        // TODO: somehow a null is being set as the result of the Future. Log this
        // case to help debug where this is occurring. When this bug is fixed this
        // condition statement should be removed.
        if (bundle == null) {
            Log.e(TAG, "the bundle must not be null", new Exception());
        }
        super.set(bundle);
    }

    public abstract void doWork() throws RemoteException;

    private Bundle internalGetResult(Long timeout, TimeUnit unit)
            throws OperationCanceledException, IOException, AuthenticatorException {
        if (!isDone()) {
            ensureNotOnMainThread();
        }
        try {
            if (timeout == null) {
                return get();
            } else {
                return get(timeout, unit);
            }
        } catch (CancellationException e) {
            throw new OperationCanceledException();
        } catch (TimeoutException e) {
            // fall through and cancel
        } catch (InterruptedException e) {
            // fall through and cancel
        } catch (ExecutionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof IOException) {
                throw (IOException) cause;
            } else if (cause instanceof UnsupportedOperationException) {
                throw new AuthenticatorException(cause);
            } else if (cause instanceof AuthenticatorException) {
                throw (AuthenticatorException) cause;
            } else if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Error) {
                throw (Error) cause;
            } else {
                throw new IllegalStateException(cause);
            }
        } finally {
            cancel(true /* interrupt if running */);
        }
        throw new OperationCanceledException();
    }

    @Override
    public Bundle getResult()
            throws OperationCanceledException, IOException, AuthenticatorException {
        return internalGetResult(null, null);
    }

    @Override
    public Bundle getResult(long timeout, TimeUnit unit)
            throws OperationCanceledException, IOException, AuthenticatorException {
        return internalGetResult(timeout, unit);
    }

    @Override
    protected void done() {
        if (mCallback != null) {
            postToHandler(mHandler, mCallback, this);
        }
    }

    /** Handles the responses from the AccountManager */
    private class Response extends IAccountManagerResponse.Stub {
        @Override
        public void onResult(Bundle bundle) {
            Intent intent = bundle.getParcelable(KEY_INTENT);
            if (intent != null && mActivity != null) {
                // since the user provided an Activity we will silently start intents
                // that we see
                mActivity.startActivity(intent);
                // leave the Future running to wait for the real response to this request
            } else if (bundle.getBoolean("retry")) {
                try {
                    doWork();
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            } else {
                set(bundle);
            }
        }

        @Override
        public void onError(int code, String message) {
            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;
            }
            setException(convertErrorToException(code, message));
        }
    }

}

start方法将调用doWork, 创建的AmsTask对象覆写了该方法, 也就是执行mService.removeAccount(mResponse, account, activity != null); 将执行AccountManagerService中对应的函数(AccountManagerService.java):

@Override
public void removeAccount(IAccountManagerResponse response, Account account,
        boolean expectActivityLaunch) {
    removeAccountAsUser(
            response,
            account,
            expectActivityLaunch,
            UserHandle.getCallingUserId());
}

@Override
public void removeAccountAsUser(IAccountManagerResponse response, Account account,
        boolean expectActivityLaunch, int userId) {
    final int callingUid = Binder.getCallingUid();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "removeAccount: " + account
                + ", response " + response
                + ", caller's uid " + callingUid
                + ", pid " + Binder.getCallingPid()
                + ", for user id " + userId);
    }
    Preconditions.checkArgument(account != null, "account cannot be null");
    Preconditions.checkArgument(response != null, "response cannot be null");

    // Only allow the system process to modify accounts of other users
    if (isCrossUser(callingUid, userId)) {
        throw new SecurityException(
                String.format(
                        "User %s tying remove account for %s" ,
                        UserHandle.getCallingUserId(),
                        userId));
    }
    /*
     * Only the system or authenticator should be allowed to remove accounts for that
     * authenticator.  This will let users remove accounts (via Settings in the system) but not
     * arbitrary applications (like competing authenticators).
     */
    UserHandle user = UserHandle.of(userId);
    if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier())
            && !isSystemUid(callingUid)) {
        String msg = String.format(
                "uid %s cannot remove accounts of type: %s",
                callingUid,
                account.type);
        throw new SecurityException(msg);
    }
    if (!canUserModifyAccounts(userId, callingUid)) {
        try {
            response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
                    "User cannot modify accounts");
        } catch (RemoteException re) {
        }
        return;
    }
    if (!canUserModifyAccountsForType(userId, account.type, callingUid)) {
        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();
    UserAccounts accounts = getUserAccounts(userId);
    cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
    synchronized(accounts.credentialsPermissionNotificationIds) {
        for (Pair<Pair<Account, String>, Integer> pair:
            accounts.credentialsPermissionNotificationIds.keySet()) {
            if (account.equals(pair.first.first)) {
                NotificationId id = accounts.credentialsPermissionNotificationIds.get(pair);
                cancelNotification(id, user);
            }
        }
    }
    final long accountId = accounts.accountsDb.findDeAccountId(account);
    logRecord(
            AccountsDb.DEBUG_ACTION_CALLED_ACCOUNT_REMOVE,
            AccountsDb.TABLE_ACCOUNTS,
            accountId,
            accounts,
            callingUid);
    try {
        new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind();
    } finally {
        restoreCallingIdentity(identityToken);
    }
}

在函数removeAccountAsUser中,最后调用new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind(); 即创建了一个RemoveAccountSession对象,然后调用其bind方法, 该类及其父类Session的实现为(AccountManagerService.java):

private class RemoveAccountSession extends Session {
    final Account mAccount;
    public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
            Account account, boolean expectActivityLaunch) {
        super(accounts, response, account.type, expectActivityLaunch,
                true /* stripAuthTokenFromResult */, account.name,
                false /* authDetailsRequired */);
        mAccount = account;
    }

    @Override
    protected String toDebugString(long now) {
        return super.toDebugString(now) + ", removeAccount"
                + ", account " + mAccount;
    }

    @Override
    public void run() throws RemoteException {
        mAuthenticator.getAccountRemovalAllowed(this, mAccount);
    }

    @Override
    public void onResult(Bundle result) {
        Bundle.setDefusable(result, true);
        if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
                && !result.containsKey(AccountManager.KEY_INTENT)) {
            final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
            if (removalAllowed) {
                removeAccountInternal(mAccounts, mAccount, getCallingUid());
            }
            IAccountManagerResponse response = getResponseAndClose();
            if (response != null) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
                            + response);
                }
                Bundle result2 = new Bundle();
                result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
                try {
                    response.onResult(result2);
                } catch (RemoteException e) {
                    // ignore
                }
            }
        }
        super.onResult(result);
    }
}

private abstract class Session extends IAccountAuthenticatorResponse.Stub
            implements IBinder.DeathRecipient, ServiceConnection {
    IAccountManagerResponse mResponse;
    final String mAccountType;
    final boolean mExpectActivityLaunch;
    final long mCreationTime;
    final String mAccountName;
    // Indicates if we need to add auth details(like last credential time)
    final boolean mAuthDetailsRequired;
    // If set, we need to update the last authenticated time. This is
    // currently
    // used on
    // successful confirming credentials.
    final boolean mUpdateLastAuthenticatedTime;

    public int mNumResults = 0;
    private int mNumRequestContinued = 0;
    private int mNumErrors = 0;

    IAccountAuthenticator mAuthenticator = null;

    private final boolean mStripAuthTokenFromResult;
    protected final UserAccounts mAccounts;

    public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
            boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
            boolean authDetailsRequired) {
        this(accounts, response, accountType, expectActivityLaunch, stripAuthTokenFromResult,
                accountName, authDetailsRequired, false /* updateLastAuthenticatedTime */);
    }

    public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
            boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
            boolean authDetailsRequired, boolean updateLastAuthenticatedTime) {
        super();
        //if (response == null) throw new IllegalArgumentException("response is null");
        if (accountType == null) throw new IllegalArgumentException("accountType is null");
        mAccounts = accounts;
        mStripAuthTokenFromResult = stripAuthTokenFromResult;
        mResponse = response;
        mAccountType = accountType;
        mExpectActivityLaunch = expectActivityLaunch;
        mCreationTime = SystemClock.elapsedRealtime();
        mAccountName = accountName;
        mAuthDetailsRequired = authDetailsRequired;
        mUpdateLastAuthenticatedTime = updateLastAuthenticatedTime;

        synchronized (mSessions) {
            mSessions.put(toString(), this);
        }
        if (response != null) {
            try {
                response.asBinder().linkToDeath(this, 0 /* flags */);
            } catch (RemoteException e) {
                mResponse = null;
                binderDied();
            }
        }
    }

    IAccountManagerResponse getResponseAndClose() {
        if (mResponse == null) {
            // this session has already been closed
            return null;
        }
        IAccountManagerResponse response = mResponse;
        close(); // this clears mResponse so we need to save the response before this call
        return response;
    }

    /**
     * Checks Intents, supplied via KEY_INTENT, to make sure that they don't violate our
     * security policy.
     *
     * In particular we want to make sure that the Authenticator doesn't try to trick users
     * into launching arbitrary intents on the device via by tricking to click authenticator
     * supplied entries in the system Settings app.
     */
    protected void checkKeyIntent(
            int authUid,
            Intent intent) throws SecurityException {
        intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
        long bid = Binder.clearCallingIdentity();
        try {
            PackageManager pm = mContext.getPackageManager();
            ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
            ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
            int targetUid = targetActivityInfo.applicationInfo.uid;
            if (!isExportedSystemActivity(targetActivityInfo)
                    && (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid,
                            targetUid))) {
                String pkgName = targetActivityInfo.packageName;
                String activityName = targetActivityInfo.name;
                String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
                        + "does not share a signature with the supplying authenticator (%s).";
                throw new SecurityException(
                        String.format(tmpl, activityName, pkgName, mAccountType));
            }
        } finally {
            Binder.restoreCallingIdentity(bid);
        }
    }

    private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
        String className = activityInfo.name;
        return "android".equals(activityInfo.packageName) &&
                (GrantCredentialsPermissionActivity.class.getName().equals(className)
                || CantAddAccountActivity.class.getName().equals(className));
    }

    private void close() {
        synchronized (mSessions) {
            if (mSessions.remove(toString()) == null) {
                // the session was already closed, so bail out now
                return;
            }
        }
        if (mResponse != null) {
            // stop listening for response deaths
            mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);

            // clear this so that we don't accidentally send any further results
            mResponse = null;
        }
        cancelTimeout();
        unbind();
    }

    @Override
    public void binderDied() {
        mResponse = null;
        close();
    }

    protected String toDebugString() {
        return toDebugString(SystemClock.elapsedRealtime());
    }

    protected String toDebugString(long now) {
        return "Session: expectLaunch " + mExpectActivityLaunch
                + ", connected " + (mAuthenticator != null)
                + ", stats (" + mNumResults + "/" + mNumRequestContinued
                + "/" + mNumErrors + ")"
                + ", lifetime " + ((now - mCreationTime) / 1000.0);
    }

    void bind() {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
        }
        if (!bindToAuthenticator(mAccountType)) {
            Log.d(TAG, "bind attempt failed for " + toDebugString());
            onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
        }
    }

    private void unbind() {
        if (mAuthenticator != null) {
            mAuthenticator = null;
            mContext.unbindService(this);
        }
    }

    public void cancelTimeout() {
        mHandler.removeMessages(MESSAGE_TIMED_OUT, this);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
        try {
            run();
        } catch (RemoteException e) {
            onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
                    "remote exception");
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mAuthenticator = null;
        IAccountManagerResponse response = getResponseAndClose();
        if (response != null) {
            try {
                response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
                        "disconnected");
            } catch (RemoteException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Session.onServiceDisconnected: "
                            + "caught RemoteException while responding", e);
                }
            }
        }
    }

    public abstract void run() throws RemoteException;

    public void onTimedOut() {
        IAccountManagerResponse response = getResponseAndClose();
        if (response != null) {
            try {
                response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
                        "timeout");
            } catch (RemoteException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
                            e);
                }
            }
        }
    }

    @Override
    public void onResult(Bundle result) {
        Bundle.setDefusable(result, true);
        mNumResults++;
        Intent intent = null;
        if (result != null) {
            boolean isSuccessfulConfirmCreds = result.getBoolean(
                    AccountManager.KEY_BOOLEAN_RESULT, false);
            boolean isSuccessfulUpdateCredsOrAddAccount =
                    result.containsKey(AccountManager.KEY_ACCOUNT_NAME)
                    && result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);
            // We should only update lastAuthenticated time, if
            // mUpdateLastAuthenticatedTime is true and the confirmRequest
            // or updateRequest was successful
            boolean needUpdate = mUpdateLastAuthenticatedTime
                    && (isSuccessfulConfirmCreds || isSuccessfulUpdateCredsOrAddAccount);
            if (needUpdate || mAuthDetailsRequired) {
                boolean accountPresent = isAccountPresentForCaller(mAccountName, mAccountType);
                if (needUpdate && accountPresent) {
                    updateLastAuthenticatedTime(new Account(mAccountName, mAccountType));
                }
                if (mAuthDetailsRequired) {
                    long lastAuthenticatedTime = -1;
                    if (accountPresent) {
                        lastAuthenticatedTime = mAccounts.accountsDb
                                .findAccountLastAuthenticatedTime(
                                        new Account(mAccountName, mAccountType));
                    }
                    result.putLong(AccountManager.KEY_LAST_AUTHENTICATED_TIME,
                            lastAuthenticatedTime);
                }
            }
        }
        if (result != null
                && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
            checkKeyIntent(
                    Binder.getCallingUid(),
                    intent);
        }
        if (result != null
                && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
            String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
            String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
            if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
                Account account = new Account(accountName, accountType);
                cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
                        new UserHandle(mAccounts.userId));
            }
        }
        IAccountManagerResponse response;
        if (mExpectActivityLaunch && result != null
                && result.containsKey(AccountManager.KEY_INTENT)) {
            response = mResponse;
        } else {
            response = getResponseAndClose();
        }
        if (response != null) {
            try {
                if (result == null) {
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, getClass().getSimpleName()
                                + " calling onError() on response " + response);
                    }
                    response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                            "null bundle returned");
                } else {
                    if (mStripAuthTokenFromResult) {
                        result.remove(AccountManager.KEY_AUTHTOKEN);
                    }
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, getClass().getSimpleName()
                                + " calling onResult() on response " + response);
                    }
                    if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
                            (intent == null)) {
                        // All AccountManager error codes are greater than 0
                        response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
                                result.getString(AccountManager.KEY_ERROR_MESSAGE));
                    } else {
                        response.onResult(result);
                    }
                }
            } catch (RemoteException e) {
                // if the caller is dead then there is no one to care about remote exceptions
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "failure while notifying response", e);
                }
            }
        }
    }

    @Override
    public void onRequestContinued() {
        mNumRequestContinued++;
    }

    @Override
    public void onError(int errorCode, String errorMessage) {
        mNumErrors++;
        IAccountManagerResponse response = getResponseAndClose();
        if (response != null) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, getClass().getSimpleName()
                        + " calling onError() on response " + response);
            }
            try {
                response.onError(errorCode, errorMessage);
            } catch (RemoteException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
                }
            }
        } else {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Session.onError: already closed");
            }
        }
    }

    /**
     * find the component name for the authenticator and initiate a bind
     * if no authenticator or the bind fails then return false, otherwise return true
     */
    private boolean bindToAuthenticator(String authenticatorType) {
        final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
        authenticatorInfo = mAuthenticatorCache.getServiceInfo(
                AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
        if (authenticatorInfo == null) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "there is no authenticator for " + authenticatorType
                        + ", bailing out");
            }
            return false;
        }

        if (!isLocalUnlockedUser(mAccounts.userId)
                && !authenticatorInfo.componentInfo.directBootAware) {
            Slog.w(TAG, "Blocking binding to authenticator " + authenticatorInfo.componentName
                    + " which isn't encryption aware");
            return false;
        }

        Intent intent = new Intent();
        intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
        intent.setComponent(authenticatorInfo.componentName);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
        }
        if (!mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
                UserHandle.of(mAccounts.userId))) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
            }
            return false;
        }

        return true;
    }
}

类RemoveAccountSession对象的bind方法实现在其父类Session中,进一步调用bindToAuthenticator, 在还函数中, 创建一个Intent对象, 其action设置为AccountManager.ACTION_AUTHENTICATOR_INTENT, component设置为自定义authenticator所在的服务.接着调用函数bindServiceAsUser, 传入的ServiceConnection回调为this, 即RemoveAccountSession对象本身, 这是因为类RemoveAccountSession的父类Session执行了接口ServiceConnection.
被bind的自定义服务需要在AndroidManifest.xml中有如下类似定义:

<service
    android:name="com.myaccount.demo.MyAuthenticatorService"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator"/>
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator" />
</service>

其中intent-filter中指定的action即为AccountManager.ACTION_AUTHENTICATOR_INTENT的值,meta-data中指定的文件中定义帐号的属性.该自定义服务的的一个简单但完备的实现如下:

public class MyAuthenticatorService extends Service {
    private MyAuthenticator mAuthenticator;

    @Override
    public void onCreate() {
        mAuthenticator = new MyAuthenticator(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder();
    }
}

自定义类MyAuthenticatorService的简单实现为:

public class MyAuthenticator extends AbstractAccountAuthenticator {

    public SyncAuthenticator(Context context) {
        super(context);
    }
    
    // Some other override methods
    
    @Override
    public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
            Account account) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
        return result;
    }
}

该自定义类MyAuthenticatorService可以覆写父类AbstractAccountAuthenticator的抽象方法.这里控制帐号能否被removeAccount删除的是getAccountRemovalAllowed方法, 如果将该方法中的Bundle对象中的字段AccountManager.KEY_BOOLEAN_RESULT,设置为false,则帐号不能被删除,如果设置为true,则帐号可以删除.自定义服务MyAuthenticatorService 中执行的mAuthenticator.getIBinder()返回的是一个IBinder对象, 该方法实现在父类AbstractAccountAuthenticator中(AbstractAccountAuthenticator.java):

public final IBinder getIBinder() {
    return mTransport.asBinder();
}

mTransport是父类AbstractAccountAuthenticator中一个私有成员变量: private Transport mTransport = new Transport();Transport是类AbstractAccountAuthenticator中的一个内部类, 其定义为(AbstractAccountAuthenticator.java):

private class Transport extends IAccountAuthenticator.Stub {

    // Implementation of some other methods in IAccountAuthenticator    

    @Override
    public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
            Account account) throws RemoteException {
        checkBinderPermission();
        try {
            final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
                new AccountAuthenticatorResponse(response), account);
            if (result != null) {
                response.onResult(result);
            }
        } catch (Exception e) {
            handleException(response, "getAccountRemovalAllowed", account.toString(), e);
        }
    }
}

所以类Transport的对象是个IBinder对象, 调用该对象的getAccountRemovalAllowed方法时, 首先调用自定义类MyAuthenticator中覆写的该方法,然后调用response.onResult(result);

函数bindServiceAsUser在bind到我们的自定义服务后,onServiceConnected回调被执行,mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);将返回类Transport对象,接着调用类RemoveAccountSession中的run方法,即调用mAuthenticator.getAccountRemovalAllowed(this, mAccount);也就是调用Transport类中的getAccountRemovalAllowed方法,其第一个输入参数为this, 即RemoveAccountSession对象,这是因为其父类Session执行了IAccountAuthenticatorResponse.Stub接口, 因此当在Transport类的方法getAccountRemovalAllowed中执行response.onResult(result);时, 将回调服务端(RemoveAccountSession)对象的对应方法onResult,该方法会检查自定义的MyAuthenticator是否允许删除帐号的返回结果,如果允许,则调用真正的帐号删除函数removeAccountInternal.

if (removalAllowed) {
    removeAccountInternal(mAccounts, mAccount, getCallingUid());
}

RemoveAccountSessiononResult方法在真正删除帐号后, 调用父类对应的onResult方法,该方法中,使用IAccountManagerResponse response;对象的onResult方法回调通知类AmsTask对象, 因为AmsTask对象发起帐号删除操作的函数doWork中, 传入的输入参数mResponse是其内部类Response对象.

public void doWork() throws RemoteException {
    mService.removeAccount(mResponse, account, false);
}

AmsTask的内部类Response是一个IAccountManagerResponse接口的服务端:

private class Response extends IAccountManagerResponse.Stub 

其函数 onResult中将继续调用set(bundle);其中调用父类FutureTask中的set方法,finishCompletion方法和done方法,类AmsTask覆写其父类FutureTask中的done方法:

@Override
protected void done() {
    if (mCallback != null) {
        postToHandler(mHandler, mCallback, this);
    }
}

mHandlermCallback是我们最开始调用removeAccount传入的参数,所以将在mHandler所在的线程回调mCallback, 将获得帐号删除的操作结果.

至此,AccountManager系统服务调用removeAccount删除帐号的总体流程结束.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值