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

Commit 15ce3387 authored by Matthew Williams's avatar Matthew Williams Committed by Android (Google) Code Review
Browse files

Merge "Make sync settings restore more robust" into mnc-dev

parents 469b5da8 53abfdb8
Loading
Loading
Loading
Loading
+42 −62
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ import android.app.backup.BackupHelper;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncAdapterType;
import android.content.SyncStatusObserver;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.Log;

@@ -47,8 +45,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
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
@@ -270,6 +266,10 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper {
                    // yet won't be restored.
                    if (currentAccounts.contains(account)) {
                        restoreExistingAccountSyncSettingsFromJSON(accountJSON);
                    } else {
                        // TODO:
                        // Stash the data to a file that the SyncManager can read from to restore
                        // settings at a later date.
                    }
                }
            } 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
     * 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)
            throws JSONException {
@@ -307,71 +332,26 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper {
        JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
        String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
        String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);

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

            // 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();
                                }
                            }
                        });
            boolean wasSyncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);
            int wasSyncable = authority.getInt(KEY_AUTHORITY_SYNC_STATE);

                // If we set sync settings for a sync that hasn't been initialized yet, we run the
                // risk of having our changes overwritten later on when the sync gets initialized.
                // 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);
            ContentResolver.setSyncAutomaticallyAsUser(
                    account, authorityName, wasSyncEnabled, 0 /* user Id */);

                boolean done = false;
                try {
                    done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    Log.e(TAG, "CountDownLatch interrupted\n" + e);
                    done = false;
                }
                if (!done) {
                    overwriteSync = false;
                    Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName
                            + "' authority.");
            if (!wasSyncEnabled) {
                ContentResolver.setIsSyncable(
                        account,
                        authorityName,
                        wasSyncable == 0 ?
                                0 /* not syncable */ : 2 /* syncable but needs initialization */);
            }
                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
    public void writeNewStateDescription(ParcelFileDescriptor newState) {
+4 −3
Original line number Diff line number Diff line
@@ -801,7 +801,7 @@ public class SyncManager {
            for (String authority : syncableAuthorities) {
                int isSyncable = getIsSyncable(account.account, account.userId,
                        authority);
                if (isSyncable == 0) {
                if (isSyncable == AuthorityInfo.NOT_SYNCABLE) {
                    continue;
                }
                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
@@ -813,8 +813,9 @@ public class SyncManager {
                final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                if (isSyncable < 0 && isAlwaysSyncable) {
                    mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
                    isSyncable = 1;
                    mSyncStorageEngine.setIsSyncable(
                            account.account, account.userId, authority, AuthorityInfo.SYNCABLE);
                    isSyncable = AuthorityInfo.SYNCABLE;
                }
                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
                    continue;
+54 −22
Original line number Diff line number Diff line
@@ -301,6 +301,30 @@ public class SyncStorageEngine extends Handler {
    }

    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 int ident;
        boolean enabled;
@@ -349,12 +373,11 @@ public class SyncStorageEngine extends Handler {
        }

        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
            backoffDelay = -1; // if < 0 then we aren't in backoff mode
            PeriodicSync defaultSync;
            // Old version is one sync a day. Empty bundle gets replaced by any addPeriodicSync()
            // call.
            // Old version is one sync a day.
            if (target.target_provider) {
                defaultSync =
                        new PeriodicSync(target.account, target.provider,
@@ -663,6 +686,12 @@ public class SyncStorageEngine extends Handler {
                }
                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;
            writeAccountInfoLocked();
        }
@@ -682,7 +711,7 @@ public class SyncStorageEngine extends Handler {
                        new EndPoint(account, providerName, userId),
                        "get authority syncable");
                if (authority == null) {
                    return -1;
                    return AuthorityInfo.NOT_INITIALIZED;
                }
                return authority.syncable;
            }
@@ -696,7 +725,7 @@ public class SyncStorageEngine extends Handler {
                    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) {
        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;
        synchronized (mAuthorities) {
            aInfo = getOrCreateAuthorityLocked(target, -1, false);
            if (syncable > 1) {
                syncable = 1;
            } else if (syncable < -1) {
                syncable = -1;
            if (syncable < AuthorityInfo.NOT_INITIALIZED) {
                syncable = AuthorityInfo.NOT_INITIALIZED;
            }
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
@@ -750,7 +778,7 @@ public class SyncStorageEngine extends Handler {
            aInfo.syncable = syncable;
            writeAccountInfoLocked();
        }
        if (syncable > 0) {
        if (syncable == AuthorityInfo.SYNCABLE) {
            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
        }
        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -2012,7 +2040,7 @@ public class SyncStorageEngine extends Handler {
            int userId = user == null ? 0 : Integer.parseInt(user);
            if (accountType == null && packageName == null) {
                accountType = "com.google";
                syncable = "unknown";
                syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED);
            }
            authority = mAuthorities.get(id);
            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
@@ -2052,11 +2080,19 @@ public class SyncStorageEngine extends Handler {
            }
            if (authority != null) {
                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)) {
                    authority.syncable = -1;
                        authority.syncable = AuthorityInfo.NOT_INITIALIZED;
                    } else {
                    authority.syncable =
                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
                        authority.syncable = Boolean.parseBoolean(syncable) ?
                                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE;
                    }

                }
            } else {
                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, "class", info.service.getClassName());
                }
                if (authority.syncable < 0) {
                    out.attribute(null, "syncable", "unknown");
                } else {
                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
                }
                out.attribute(null, "syncable", Integer.toString(authority.syncable));
                for (PeriodicSync periodicSync : authority.periodicSyncs) {
                    out.startTag(null, "periodicSync");
                    out.attribute(null, "period", Long.toString(periodicSync.period));