Search code examples
androidaccountmanagerandroid-syncadapter

Does SyncAdapter get notified when AccountManager removes account?


So, my question restated is when you go to Settings -> Accounts & Sync and select the an account that was created that your SyncAdapter is syncing with a cloud server, and select remove account, what happens as far as your SyncAdapter is concerned? There is a dialog that displays asking you to confirm and that the data on the phone associated with that account will be removed. I cannot easily believe that the framework can automatically remove the data my SyncAdapter has stored in the local database, but it seems to imply that removing the account will (and I would agree that is should) remove that data. Is there a addition to my SyncAdapter that will serve sort of as the callback for the account removal to handle deleting all the appropriate data from the local database? Maybe it has to be done through the AccountManager instead; my AccountManager gets notified when the account gets removed and from there I can trigger the data deletion without the SyncAdapter.

EDIT: On a related note, is the sync manager calling my SyncAdapter for each account that it synchronizes when a new account is added? I see a onPerformSync(...) being executed for previously added accounts along with the just added account when I add an account, and would like to stop that.


Solution

  • I discovered the solution is to make the app's ContentProvider implement OnAccountsUpdateListener. Attach the ContentProvider as a listener in its onCreate method with account_manager.addOnAccountsUpdatedListener(this, null, false) and then implement the interface method like

    @Override
    public void onAccountsUpdated(final Account[] accounts) {
        Ln.i("Accounts updated.");
        final Iterable<String> account_list = new Iterable<String>() {
            @Override
            public Iterator<String> iterator() {
                return new Iterator<String>() {
                    private final Iterator<Account> account_list = Arrays.asList(accounts).iterator();
    
                    @Override
                    public boolean hasNext() {
                        return account_list.hasNext();
                    }
    
                    /** Extracts the next account name and wraps it in single quotes. */
                    @Override
                    public String next() {
                        return "'" + account_list.next().name + "'";
                    }
    
                    @Override
                    public void remove() { throw new UnsupportedOperationException("Not implemented"); }
                };
            }
        };
        final String account_set = TextUtils.join(", ", account_list);
        Ln.i("Current accounts: %s", account_set);
    
        // Removes content that is associated with accounts that are not currently connected
        final SelectionBuilder builder = new SelectionBuilder();
        builder.table(Tables.CALENDARS)
               .where(Calendars.CALENDAR_USER + " NOT IN (?)", account_set);
    
        new SafeAsyncTask() {
            @Override
            public Void call() throws Exception {
                _model.openWritableDatabase();
                _model.delete(builder);
                return null;
            }
        }.execute();
    
    
        getContext().getContentResolver().notifyChange(Calendars.NO_SYNC_URI, null, false);
    }
    

    I construct a String of the currently connected accounts, then build a SQL query with that String. I perform a delete on the database in a background thread on that query to remove the data associated with accounts not currently connected. And I notify that content changed, but does not need to synchronized with the server.