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

Commit c8b7971c authored by Fred Quintana's avatar Fred Quintana Committed by Android (Google) Code Review
Browse files

Merge "Make a separate active sync queue for initialization and regular syncs."

parents 572eab6f 918339ab
Loading
Loading
Loading
Loading
+493 −439

File changed.

Preview size limit exceeded, changes collapsed.

+54 −19
Original line number Diff line number Diff line
@@ -33,9 +33,12 @@ public class SyncOperation implements Comparable {
    public long earliestRunTime;
    public boolean expedited;
    public SyncStorageEngine.PendingOperation pendingOperation;
    public Long backoff;
    public long delayUntil;
    public long effectiveRunTime;

    public SyncOperation(Account account, int source, String authority, Bundle extras,
            long delayInMs) {
            long delayInMs, long backoff, long delayUntil) {
        this.account = account;
        this.syncSource = source;
        this.authority = authority;
@@ -48,6 +51,8 @@ public class SyncOperation implements Comparable {
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
        this.delayUntil = delayUntil;
        this.backoff = backoff;
        final long now = SystemClock.elapsedRealtime();
        if (delayInMs < 0) {
            this.expedited = true;
@@ -56,6 +61,7 @@ public class SyncOperation implements Comparable {
            this.expedited = false;
            this.earliestRunTime = now + delayInMs;
        }
        updateEffectiveRunTime();
        this.key = toKey();
    }

@@ -72,49 +78,78 @@ public class SyncOperation implements Comparable {
        this.extras = new Bundle(other.extras);
        this.expedited = other.expedited;
        this.earliestRunTime = SystemClock.elapsedRealtime();
        this.backoff = other.backoff;
        this.delayUntil = other.delayUntil;
        this.updateEffectiveRunTime();
        this.key = toKey();
    }

    public String toString() {
        return dump(true);
    }

    public String dump(boolean useOneLine) {
        StringBuilder sb = new StringBuilder();
        sb.append("authority: ").append(authority);
        sb.append(" account: ").append(account);
        sb.append(" extras: ");
        extrasToStringBuilder(extras, sb, false /* asKey */);
        sb.append(" syncSource: ").append(syncSource);
        sb.append(" when: ").append(earliestRunTime);
        sb.append(" expedited: ").append(expedited);
        sb.append(account.name);
        sb.append(" (" + account.type + ")");
        sb.append(", " + authority);
        sb.append(", ");
        sb.append(SyncStorageEngine.SOURCES[syncSource]);
        sb.append(", earliestRunTime " + earliestRunTime);
        if (expedited) {
            sb.append(", EXPEDITED");
        }
        if (!useOneLine && !extras.keySet().isEmpty()) {
            sb.append("\n    ");
            extrasToStringBuilder(extras, sb);
        }
        return sb.toString();
    }

    public boolean isInitialization() {
        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
    }

    public boolean ignoreBackoff() {
        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
    }

    private String toKey() {
        StringBuilder sb = new StringBuilder();
        sb.append("authority: ").append(authority);
    	sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
        sb.append(" extras: ");
        extrasToStringBuilder(extras, sb, true /* asKey */);
        extrasToStringBuilder(extras, sb);
        return sb.toString();
    }

    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
        sb.append("[");
        for (String key : bundle.keySet()) {
            // if we are writing this as a key don't consider whether this
            // is an initialization sync or not when computing the key since
            // we set this flag appropriately when dispatching the sync request.
            if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
                continue;
            }
            sb.append(key).append("=").append(bundle.get(key)).append(" ");
        }
        sb.append("]");
    }

    public void updateEffectiveRunTime() {
        effectiveRunTime = ignoreBackoff()
                ? earliestRunTime
                : Math.max(
                    Math.max(earliestRunTime, delayUntil),
                    backoff);
    }

    public int compareTo(Object o) {
        SyncOperation other = (SyncOperation)o;
        if (earliestRunTime == other.earliestRunTime) {

        if (expedited != other.expedited) {
            return expedited ? -1 : 1;
        }

        if (effectiveRunTime == other.effectiveRunTime) {
            return 0;
        }
        return (earliestRunTime < other.earliestRunTime) ? -1 : 1;

        return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
    }
}
+32 −59
Original line number Diff line number Diff line
@@ -16,17 +16,18 @@

package android.content;

import com.google.android.collect.Lists;
import com.google.android.collect.Maps;

import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 *
@@ -38,7 +39,7 @@ public class SyncQueue {

    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
    // quick lookup of an enqueued SyncOperation.
    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
    public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();

    public SyncQueue(SyncStorageEngine syncStorageEngine) {
        mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@ public class SyncQueue {
        final int N = ops.size();
        for (int i=0; i<N; i++) {
            SyncStorageEngine.PendingOperation op = ops.get(i);
            final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
            SyncOperation syncOperation = new SyncOperation(
                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
                    backoff != null ? backoff.first : 0,
                    syncStorageEngine.getDelayUntilTime(op.account, op.authority));
            syncOperation.expedited = op.expedited;
            syncOperation.pendingOperation = op;
            add(syncOperation, op);
@@ -119,65 +123,26 @@ public class SyncQueue {
        }
    }

    /**
     * Find the operation that should run next. Operations are sorted by their earliestRunTime,
     * prioritizing first those with a syncable state of "unknown" that aren't retries then
     * expedited operations.
     * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
     * @return the operation that should run next and when it should run. The time may be in
     * the future. It is expressed in milliseconds since boot.
     */
    public Pair<SyncOperation, Long> nextOperation() {
        SyncOperation best = null;
        long bestRunTime = 0;
        boolean bestSyncableIsUnknownAndNotARetry = false;
    public void onBackoffChanged(Account account, String providerName, long backoff) {
        // for each op that matches the account and provider update its
        // backoff and effectiveStartTime
        for (SyncOperation op : mOperationsMap.values()) {
            long opRunTime = op.earliestRunTime;
            if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
                Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
                long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
                opRunTime = Math.max(
                        Math.max(opRunTime, delayUntil),
                        backoff != null ? backoff.first : 0);
            }
            // we know a sync is a retry if the intialization flag is set, since that will only
            // be set by the sync dispatching code, thus if it is set it must have already been
            // dispatched
            final boolean syncableIsUnknownAndNotARetry =
                    !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
                    && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
            // if the unsyncable state differs, make the current the best if it is unsyncable
            // else, if the expedited state differs, make the current the best if it is expedited
            // else, make the current the best if it is earlier than the best
            if (best == null
                    || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
                        ? (best.expedited == op.expedited
                           ? opRunTime < bestRunTime
                           : op.expedited)
                        : syncableIsUnknownAndNotARetry)) {
                best = op;
                bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
                bestRunTime = opRunTime;
            }
        }
        if (best == null) {
            return null;
        }
        return Pair.create(best, bestRunTime);
            if (op.account.equals(account) && op.authority.equals(providerName)) {
                op.backoff = backoff;
                op.updateEffectiveRunTime();
            }
        }
    }

    /**
     * Find and return the SyncOperation that should be run next and is ready to run.
     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
     * decide if the sync operation is ready to run
     * @return the SyncOperation that should be run next and is ready to run.
     */
    public Pair<SyncOperation, Long> nextReadyToRun(long now) {
        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
            return null;
    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
        // for each op that matches the account and provider update its
        // delayUntilTime and effectiveStartTime
        for (SyncOperation op : mOperationsMap.values()) {
            if (op.account.equals(account) && op.authority.equals(providerName)) {
                op.delayUntil = delayUntil;
                op.updateEffectiveRunTime();
            }
        }
        return nextOpAndRunTime;
    }

    public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@ public class SyncQueue {
    }

    public void dump(StringBuilder sb) {
        final long now = SystemClock.elapsedRealtime();
        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
        for (SyncOperation operation : mOperationsMap.values()) {
            sb.append(operation).append("\n");
            sb.append("  ");
            if (operation.effectiveRunTime <= now) {
                sb.append("READY");
            } else {
                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
            }
            sb.append(" - ");
            sb.append(operation.dump(false)).append("\n");
        }
    }
}
+57 −45
Original line number Diff line number Diff line
@@ -229,7 +229,7 @@ public class SyncStorageEngine extends Handler {
    private final ArrayList<PendingOperation> mPendingOperations =
            new ArrayList<PendingOperation>();

    private SyncInfo mCurrentSync;
    private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();

    private final SparseArray<SyncStatusInfo> mSyncStatus =
            new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@ public class SyncStorageEngine extends Handler {

    /**
     * Returns true if there is currently a sync operation for the given
     * account or authority in the pending list, or actively being processed.
     * account or authority actively being processed.
     */
    public boolean isSyncActive(Account account, String authority) {
        synchronized (mAuthorities) {
            int i = mPendingOperations.size();
            while (i > 0) {
                i--;
                // TODO(fredq): this probably shouldn't be considering
                // pending operations.
                PendingOperation op = mPendingOperations.get(i);
                if (op.account.equals(account) && op.authority.equals(authority)) {
                    return true;
                }
            }

            if (mCurrentSync != null) {
                AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
            for (SyncInfo syncInfo : mCurrentSyncs) {
                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
                if (ainfo != null && ainfo.account.equals(account)
                        && ainfo.authority.equals(authority)) {
                    return true;
@@ -887,13 +876,12 @@ public class SyncStorageEngine extends Handler {
    }

    /**
     * Called when the currently active sync is changing (there can only be
     * one at a time).  Either supply a valid ActiveSyncContext with information
     * about the sync, or null to stop the currently active sync.
     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
     * about the sync.
     */
    public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
        final SyncInfo syncInfo;
        synchronized (mAuthorities) {
            if (activeSyncContext != null) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "setActiveSync: account="
                    + activeSyncContext.mSyncOperation.account
@@ -901,26 +889,34 @@ public class SyncStorageEngine extends Handler {
                    + " src=" + activeSyncContext.mSyncOperation.syncSource
                    + " extras=" + activeSyncContext.mSyncOperation.extras);
            }
                if (mCurrentSync != null) {
                    Log.w(TAG, "setActiveSync called with existing active sync!");
                }
                AuthorityInfo authority = getAuthorityLocked(
            AuthorityInfo authority = getOrCreateAuthorityLocked(
                    activeSyncContext.mSyncOperation.account,
                    activeSyncContext.mSyncOperation.authority,
                        "setActiveSync");
                if (authority == null) {
                    return;
                }
                mCurrentSync = new SyncInfo(authority.ident,
                    -1 /* assign a new identifier if creating a new authority */,
                    true /* write to storage if this results in a change */);
            syncInfo = new SyncInfo(authority.ident,
                    authority.account, authority.authority,
                    activeSyncContext.mStartTime);
            } else {
                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
                mCurrentSync = null;
            mCurrentSyncs.add(syncInfo);
        }

        reportActiveChange();
        return syncInfo;
    }

        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
    /**
     * Called to indicate that a previously active sync is no longer active.
     */
    public void removeActiveSync(SyncInfo syncInfo) {
        synchronized (mAuthorities) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "removeActiveSync: account="
                        + syncInfo.account + " auth=" + syncInfo.authority);
            }
            mCurrentSyncs.remove(syncInfo);
        }

        reportActiveChange();
    }

    /**
@@ -1095,10 +1091,26 @@ public class SyncStorageEngine extends Handler {
     * Return the currently active sync information, or null if there is no
     * active sync.  Note that the returned object is the real, live active
     * sync object, so be careful what you do with it.
     * <p>
     * Since multiple concurrent syncs are now supported you should use
     * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
     * This method returns the first item from the list of current syncs
     * or null if there are none.
     * @deprecated use {@link #getCurrentSyncs()}
     */
    public SyncInfo getCurrentSync() {
        synchronized (mAuthorities) {
            return mCurrentSync;
            return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
        }
    }

    /**
     * Return a list of the currently active syncs. Note that the returned items are the
     * real, live active sync objects, so be careful what you do with it.
     */
    public List<SyncInfo> getCurrentSyncs() {
        synchronized (mAuthorities) {
            return new ArrayList<SyncInfo>(mCurrentSyncs);
        }
    }

+0 −164
Original line number Diff line number Diff line
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content;

import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContext;
import android.test.mock.MockContentResolver;
import android.accounts.Account;
import android.os.Bundle;
import android.os.SystemClock;

public class SyncQueueTest extends AndroidTestCase {
    private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
    private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
    private static final String AUTHORITY1 = "test.authority1";
    private static final String AUTHORITY2 = "test.authority2";
    private static final String AUTHORITY3 = "test.authority3";

    private SyncStorageEngine mSettings;
    private SyncQueue mSyncQueue;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        MockContentResolver mockResolver = new MockContentResolver();
        mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
        mSyncQueue = new SyncQueue(mSettings);
    }

    public void testSyncQueueOrder() throws Exception {
        final SyncOperation op1 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
        final SyncOperation op2 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
        final SyncOperation op3 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
        final SyncOperation op4 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
        final SyncOperation op5 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
        final SyncOperation op6 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
        op6.expedited = true;

        mSyncQueue.add(op1);
        mSyncQueue.add(op2);
        mSyncQueue.add(op3);
        mSyncQueue.add(op4);
        mSyncQueue.add(op5);
        mSyncQueue.add(op6);

        long now = SystemClock.elapsedRealtime() + 200;

        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op6);

        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op1);

        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op4);

        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op5);

        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op2);

        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op3);
    }

    public void testOrderWithBackoff() throws Exception {
        final SyncOperation op1 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
        final SyncOperation op2 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
        final SyncOperation op3 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
        final SyncOperation op4 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
        final SyncOperation op5 = new SyncOperation(
                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
        final SyncOperation op6 = new SyncOperation(
                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
        op6.expedited = true;

        mSyncQueue.add(op1);
        mSyncQueue.add(op2);
        mSyncQueue.add(op3);
        mSyncQueue.add(op4);
        mSyncQueue.add(op5);
        mSyncQueue.add(op6);

        long now = SystemClock.elapsedRealtime() + 200;

        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op6);

        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op1);

        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, now + 200, 5);
        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);

        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);

        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, now + 200);
        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);

        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, 0);
        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op4);

        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op5);

        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op2);

        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
        mSyncQueue.remove(op3);
    }

    Bundle newTestBundle(String val) {
        Bundle bundle = new Bundle();
        bundle.putString("test", val);
        return bundle;
    }

    static class TestContext extends ContextWrapper {
        ContentResolver mResolver;

        public TestContext(ContentResolver resolver, Context realContext) {
            super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
            mResolver = resolver;
        }

        @Override
        public void enforceCallingOrSelfPermission(String permission, String message) {
        }

        @Override
        public ContentResolver getContentResolver() {
            return mResolver;
        }
    }
}