Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53abfdb8 authored by Matthew Williams's avatar Matthew Williams
Browse files

Make sync settings restore more robust

Bug: 18506992
Parent Bug: 17967106
Introduce a new state for ContentResolver#getIsSyncable.
This state specifies that an adapter should be disabled until
explicitly turned on by ContentResolver#setSyncAutomatically(true).
In this way we can restore disabled sync adapters and still allow
them to run their initialization logic later on when they are
re-enabled.

Change-Id: I03fd1f994c4bc982bbc723154ba20bb252efdf80
parent 89285039
Loading
Loading
Loading
Loading
+42 −62
Original line number Original line Diff line number Diff line
@@ -28,8 +28,6 @@ import android.app.backup.BackupHelper;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.SyncAdapterType;
import android.content.SyncAdapterType;
import android.content.SyncStatusObserver;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.Log;


@@ -47,8 +45,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


/**
/**
 * Helper for backing up account sync settings (whether or not a service should be synced). The
 * Helper for backing up account sync settings (whether or not a service should be synced). The
@@ -270,6 +266,10 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper {
                    // yet won't be restored.
                    // yet won't be restored.
                    if (currentAccounts.contains(account)) {
                    if (currentAccounts.contains(account)) {
                        restoreExistingAccountSyncSettingsFromJSON(accountJSON);
                        restoreExistingAccountSyncSettingsFromJSON(accountJSON);
                    } else {
                        // TODO:
                        // Stash the data to a file that the SyncManager can read from to restore
                        // settings at a later date.
                    }
                    }
                }
                }
            } finally {
            } finally {
@@ -300,6 +300,31 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper {
    /**
    /**
     * Restore account sync settings using the given JSON. This function won't work if the account
     * Restore account sync settings using the given JSON. This function won't work if the account
     * doesn't exist yet.
     * doesn't exist yet.
     * This function will only be called during Setup Wizard, where we are guaranteed that there
     * are no active syncs.
     * There are 2 pieces of data to restore -
     *      isSyncable (corresponds to {@link ContentResolver#getIsSyncable(Account, String)}
     *      syncEnabled (corresponds to {@link ContentResolver#getSyncAutomatically(Account, String)}
     * <strong>The restore favours adapters that were enabled on the old device, and doesn't care
     * about adapters that were disabled.</strong>
     *
     * syncEnabled=true in restore data.
     * syncEnabled will be true on this device. isSyncable will be left as the default in order to
     * give the enabled adapter the chance to run an initialization sync.
     *
     * syncEnabled=false in restore data.
     * syncEnabled will be false on this device. isSyncable will be set to 2, unless it was 0 on the
     * old device in which case it will be set to 0 on this device. This is because isSyncable=0 is
     * a rare state and was probably set to 0 for good reason (historically isSyncable is a way by
     * which adapters control their own sync state independently of sync settings which is
     * toggleable by the user).
     * isSyncable=2 is a new isSyncable state we introduced specifically to allow adapters that are
     * disabled after a restore to run initialization logic when the adapter is later enabled.
     * See com.android.server.content.SyncStorageEngine#setSyncAutomatically
     *
     * The end result is that an adapter that the user had on will be turned on and get an
     * initialization sync, while an adapter that the user had off will be off until the user
     * enables it on this device at which point it will get an initialization sync.
     */
     */
    private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON)
    private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON)
            throws JSONException {
            throws JSONException {
@@ -307,71 +332,26 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper {
        JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
        JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
        String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
        String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
        String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
        String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);

        final Account account = new Account(accountName, accountType);
        final Account account = new Account(accountName, accountType);
        for (int i = 0; i < authorities.length(); i++) {
        for (int i = 0; i < authorities.length(); i++) {
            JSONObject authority = (JSONObject) authorities.get(i);
            JSONObject authority = (JSONObject) authorities.get(i);
            final String authorityName = authority.getString(KEY_AUTHORITY_NAME);
            final String authorityName = authority.getString(KEY_AUTHORITY_NAME);
            boolean syncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);
            boolean wasSyncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);

            int wasSyncable = authority.getInt(KEY_AUTHORITY_SYNC_STATE);
            // Cancel any active syncs.
            if (ContentResolver.isSyncActive(account, authorityName)) {
                ContentResolver.cancelSync(account, authorityName);
            }

            boolean overwriteSync = true;
            Bundle initializationExtras = createSyncInitializationBundle();
            int currentSyncState = ContentResolver.getIsSyncable(account, authorityName);
            if (currentSyncState < 0) {
                // Requesting a sync is an asynchronous operation, so we setup a countdown latch to
                // wait for it to finish. Initialization syncs are generally very brief and
                // shouldn't take too much time to finish.
                final CountDownLatch latch = new CountDownLatch(1);
                Object syncStatusObserverHandle = ContentResolver.addStatusChangeListener(
                        ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncStatusObserver() {
                            @Override
                            public void onStatusChanged(int which) {
                                if (!ContentResolver.isSyncActive(account, authorityName)) {
                                    latch.countDown();
                                }
                            }
                        });


                // If we set sync settings for a sync that hasn't been initialized yet, we run the
            ContentResolver.setSyncAutomaticallyAsUser(
                // risk of having our changes overwritten later on when the sync gets initialized.
                    account, authorityName, wasSyncEnabled, 0 /* user Id */);
                // To prevent this from happening we will manually initiate the sync adapter. We
                // also explicitly pass in a Bundle with SYNC_EXTRAS_INITIALIZE to prevent a data
                // sync from running after the initialization sync. Two syncs will be scheduled, but
                // the second one (data sync) will override the first one (initialization sync) and
                // still behave as an initialization sync because of the Bundle.
                ContentResolver.requestSync(account, authorityName, initializationExtras);


                boolean done = false;
            if (!wasSyncEnabled) {
                try {
                ContentResolver.setIsSyncable(
                    done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                        account,
                } catch (InterruptedException e) {
                        authorityName,
                    Log.e(TAG, "CountDownLatch interrupted\n" + e);
                        wasSyncable == 0 ?
                    done = false;
                                0 /* not syncable */ : 2 /* syncable but needs initialization */);
                }
                if (!done) {
                    overwriteSync = false;
                    Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName
                            + "' authority.");
            }
            }
                ContentResolver.removeStatusChangeListener(syncStatusObserverHandle);
            }

            if (overwriteSync) {
                ContentResolver.setSyncAutomatically(account, authorityName, syncEnabled);
                Log.i(TAG, "Set sync automatically for '" + authorityName + "': " + syncEnabled);
        }
        }
    }
    }
    }

    private Bundle createSyncInitializationBundle() {
        Bundle extras = new Bundle();
        extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
        return extras;
    }


    @Override
    @Override
    public void writeNewStateDescription(ParcelFileDescriptor newState) {
    public void writeNewStateDescription(ParcelFileDescriptor newState) {
+4 −3
Original line number Original line Diff line number Diff line
@@ -801,7 +801,7 @@ public class SyncManager {
            for (String authority : syncableAuthorities) {
            for (String authority : syncableAuthorities) {
                int isSyncable = getIsSyncable(account.account, account.userId,
                int isSyncable = getIsSyncable(account.account, account.userId,
                        authority);
                        authority);
                if (isSyncable == 0) {
                if (isSyncable == AuthorityInfo.NOT_SYNCABLE) {
                    continue;
                    continue;
                }
                }
                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
@@ -813,8 +813,9 @@ public class SyncManager {
                final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                if (isSyncable < 0 && isAlwaysSyncable) {
                if (isSyncable < 0 && isAlwaysSyncable) {
                    mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
                    mSyncStorageEngine.setIsSyncable(
                    isSyncable = 1;
                            account.account, account.userId, authority, AuthorityInfo.SYNCABLE);
                    isSyncable = AuthorityInfo.SYNCABLE;
                }
                }
                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
                    continue;
                    continue;
+54 −22
Original line number Original line Diff line number Diff line
@@ -301,6 +301,30 @@ public class SyncStorageEngine extends Handler {
    }
    }


    public static class AuthorityInfo {
    public static class AuthorityInfo {
        // Legal values of getIsSyncable
        /**
         * Default state for a newly installed adapter. An uninitialized adapter will receive an
         * initialization sync which are governed by a different set of rules to that of regular
         * syncs.
         */
        public static final int NOT_INITIALIZED = -1;
        /**
         * The adapter will not receive any syncs. This is behaviourally equivalent to
         * setSyncAutomatically -> false. However setSyncAutomatically is surfaced to the user
         * while this is generally meant to be controlled by the developer.
         */
        public static final int NOT_SYNCABLE = 0;
        /**
         * The adapter is initialized and functioning. This is the normal state for an adapter.
         */
        public static final int SYNCABLE = 1;
        /**
         * The adapter is syncable but still requires an initialization sync. For example an adapter
         * than has been restored from a previous device will be in this state. Not meant for
         * external use.
         */
        public static final int SYNCABLE_NOT_INITIALIZED = 2;

        final EndPoint target;
        final EndPoint target;
        final int ident;
        final int ident;
        boolean enabled;
        boolean enabled;
@@ -349,12 +373,11 @@ public class SyncStorageEngine extends Handler {
        }
        }


        private void defaultInitialisation() {
        private void defaultInitialisation() {
            syncable = -1; // default to "unknown"
            syncable = NOT_INITIALIZED; // default to "unknown"
            backoffTime = -1; // if < 0 then we aren't in backoff mode
            backoffTime = -1; // if < 0 then we aren't in backoff mode
            backoffDelay = -1; // if < 0 then we aren't in backoff mode
            backoffDelay = -1; // if < 0 then we aren't in backoff mode
            PeriodicSync defaultSync;
            PeriodicSync defaultSync;
            // Old version is one sync a day. Empty bundle gets replaced by any addPeriodicSync()
            // Old version is one sync a day.
            // call.
            if (target.target_provider) {
            if (target.target_provider) {
                defaultSync =
                defaultSync =
                        new PeriodicSync(target.account, target.provider,
                        new PeriodicSync(target.account, target.provider,
@@ -663,6 +686,12 @@ public class SyncStorageEngine extends Handler {
                }
                }
                return;
                return;
            }
            }
            // If the adapter was syncable but missing its initialization sync, set it to
            // uninitialized now. This is to give it a chance to run any one-time initialization
            // logic.
            if (sync && authority.syncable == AuthorityInfo.SYNCABLE_NOT_INITIALIZED) {
                authority.syncable = AuthorityInfo.NOT_INITIALIZED;
            }
            authority.enabled = sync;
            authority.enabled = sync;
            writeAccountInfoLocked();
            writeAccountInfoLocked();
        }
        }
@@ -682,7 +711,7 @@ public class SyncStorageEngine extends Handler {
                        new EndPoint(account, providerName, userId),
                        new EndPoint(account, providerName, userId),
                        "get authority syncable");
                        "get authority syncable");
                if (authority == null) {
                if (authority == null) {
                    return -1;
                    return AuthorityInfo.NOT_INITIALIZED;
                }
                }
                return authority.syncable;
                return authority.syncable;
            }
            }
@@ -696,7 +725,7 @@ public class SyncStorageEngine extends Handler {
                    return authorityInfo.syncable;
                    return authorityInfo.syncable;
                }
                }
            }
            }
            return -1;
            return AuthorityInfo.NOT_INITIALIZED;
        }
        }
    }
    }


@@ -720,7 +749,8 @@ public class SyncStorageEngine extends Handler {
    }
    }


    public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
    public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
        setSyncableStateForEndPoint(new EndPoint(cname, userId), active ? 1 : 0);
        setSyncableStateForEndPoint(new EndPoint(cname, userId), active ?
                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
    }
    }


    /**
    /**
@@ -733,10 +763,8 @@ public class SyncStorageEngine extends Handler {
        AuthorityInfo aInfo;
        AuthorityInfo aInfo;
        synchronized (mAuthorities) {
        synchronized (mAuthorities) {
            aInfo = getOrCreateAuthorityLocked(target, -1, false);
            aInfo = getOrCreateAuthorityLocked(target, -1, false);
            if (syncable > 1) {
            if (syncable < AuthorityInfo.NOT_INITIALIZED) {
                syncable = 1;
                syncable = AuthorityInfo.NOT_INITIALIZED;
            } else if (syncable < -1) {
                syncable = -1;
            }
            }
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
                Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
@@ -750,7 +778,7 @@ public class SyncStorageEngine extends Handler {
            aInfo.syncable = syncable;
            aInfo.syncable = syncable;
            writeAccountInfoLocked();
            writeAccountInfoLocked();
        }
        }
        if (syncable > 0) {
        if (syncable == AuthorityInfo.SYNCABLE) {
            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
        }
        }
        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -2012,7 +2040,7 @@ public class SyncStorageEngine extends Handler {
            int userId = user == null ? 0 : Integer.parseInt(user);
            int userId = user == null ? 0 : Integer.parseInt(user);
            if (accountType == null && packageName == null) {
            if (accountType == null && packageName == null) {
                accountType = "com.google";
                accountType = "com.google";
                syncable = "unknown";
                syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED);
            }
            }
            authority = mAuthorities.get(id);
            authority = mAuthorities.get(id);
            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
@@ -2052,11 +2080,19 @@ public class SyncStorageEngine extends Handler {
            }
            }
            if (authority != null) {
            if (authority != null) {
                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
                try {
                    authority.syncable = (syncable == null) ?
                            AuthorityInfo.NOT_INITIALIZED : Integer.parseInt(syncable);
                } catch (NumberFormatException e) {
                    // On L we stored this as {"unknown", "true", "false"} so fall back to this
                    // format.
                    if ("unknown".equals(syncable)) {
                    if ("unknown".equals(syncable)) {
                    authority.syncable = -1;
                        authority.syncable = AuthorityInfo.NOT_INITIALIZED;
                    } else {
                    } else {
                    authority.syncable =
                        authority.syncable = Boolean.parseBoolean(syncable) ?
                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
                                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE;
                    }

                }
                }
            } else {
            } else {
                Log.w(TAG, "Failure adding authority: account="
                Log.w(TAG, "Failure adding authority: account="
@@ -2190,11 +2226,7 @@ public class SyncStorageEngine extends Handler {
                    out.attribute(null, "package", info.service.getPackageName());
                    out.attribute(null, "package", info.service.getPackageName());
                    out.attribute(null, "class", info.service.getClassName());
                    out.attribute(null, "class", info.service.getClassName());
                }
                }
                if (authority.syncable < 0) {
                out.attribute(null, "syncable", Integer.toString(authority.syncable));
                    out.attribute(null, "syncable", "unknown");
                } else {
                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
                }
                for (PeriodicSync periodicSync : authority.periodicSyncs) {
                for (PeriodicSync periodicSync : authority.periodicSyncs) {
                    out.startTag(null, "periodicSync");
                    out.startTag(null, "periodicSync");
                    out.attribute(null, "period", Long.toString(periodicSync.period));
                    out.attribute(null, "period", Long.toString(periodicSync.period));