root/sync/test/android/javatests/src/org/chromium/sync/test/util/MockSyncContentResolverDelegate.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. addStatusChangeListener
  2. removeStatusChangeListener
  3. setMasterSyncAutomatically
  4. getMasterSyncAutomatically
  5. getSyncAutomatically
  6. setSyncAutomatically
  7. setIsSyncable
  8. getIsSyncable
  9. createKey
  10. notifyObservers
  11. waitForLastNotificationCompleted
  12. disableObserverNotifications
  13. notifyObserverAsync

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.sync.test.util;


import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SyncStatusObserver;
import android.os.AsyncTask;

import junit.framework.Assert;

import org.chromium.base.ThreadUtils;
import org.chromium.sync.notifier.SyncContentResolverDelegate;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;


/**
 * Mock implementation of the {@link SyncContentResolverDelegate}.
 *
 * This implementation only supports status change listeners for the type
 * SYNC_OBSERVER_TYPE_SETTINGS.
 */
public class MockSyncContentResolverDelegate implements SyncContentResolverDelegate {

    private final Set<String> mSyncAutomaticallySet;
    private final Map<String, Boolean> mIsSyncableMap;
    private final Object mSyncableMapLock = new Object();

    private final Set<AsyncSyncStatusObserver> mObservers;

    private boolean mMasterSyncAutomatically;
    private boolean mDisableObserverNotifications;

    private Semaphore mPendingObserverCount;

    public MockSyncContentResolverDelegate() {
        mSyncAutomaticallySet = new HashSet<String>();
        mIsSyncableMap = new HashMap<String, Boolean>();
        mObservers = new HashSet<AsyncSyncStatusObserver>();
    }

    @Override
    public Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
        if (mask != ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) {
            throw new IllegalArgumentException("This implementation only supports "
                    + "ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS as the mask");
        }
        AsyncSyncStatusObserver asyncSyncStatusObserver = new AsyncSyncStatusObserver(callback);
        synchronized (mObservers) {
            mObservers.add(asyncSyncStatusObserver);
        }
        return asyncSyncStatusObserver;
    }

    @Override
    public void removeStatusChangeListener(Object handle) {
        synchronized (mObservers) {
            mObservers.remove(handle);
        }
    }

    @Override
    public void setMasterSyncAutomatically(boolean sync) {
        if (mMasterSyncAutomatically == sync) return;

        mMasterSyncAutomatically = sync;
        notifyObservers();
    }

    @Override
    public boolean getMasterSyncAutomatically() {
        return mMasterSyncAutomatically;
    }

    @Override
    public boolean getSyncAutomatically(Account account, String authority) {
        String key = createKey(account, authority);
        synchronized (mSyncableMapLock) {
            return mSyncAutomaticallySet.contains(key);
        }
    }

    @Override
    public void setSyncAutomatically(Account account, String authority, boolean sync) {
        String key = createKey(account, authority);
        synchronized (mSyncableMapLock) {
            if (!mIsSyncableMap.containsKey(key) || !mIsSyncableMap.get(key)) {
                throw new IllegalArgumentException("Account " + account +
                        " is not syncable for authority " + authority +
                        ". Can not set sync state to " + sync);
            }
            if (sync) {
                mSyncAutomaticallySet.add(key);
            } else if (mSyncAutomaticallySet.contains(key)) {
                mSyncAutomaticallySet.remove(key);
            }
        }
        notifyObservers();
    }

    @Override
    public void setIsSyncable(Account account, String authority, int syncable) {
        String key = createKey(account, authority);

        synchronized (mSyncableMapLock) {
            switch (syncable) {
                case 0:
                    if (mSyncAutomaticallySet.contains(key)) {
                        mSyncAutomaticallySet.remove(key);
                    }

                    mIsSyncableMap.put(key, false);
                    break;
                case 1:
                    mIsSyncableMap.put(key, true);
                    break;
                case -1:
                    if (mIsSyncableMap.containsKey(key)) {
                        mIsSyncableMap.remove(key);
                    }
                    break;
                default:
                    throw new IllegalArgumentException("Unable to understand syncable argument: " +
                            syncable);
            }
        }
        notifyObservers();
    }

    @Override
    public int getIsSyncable(Account account, String authority) {
        String key = createKey(account, authority);
        synchronized (mSyncableMapLock) {
            if (mIsSyncableMap.containsKey(key)) {
                return mIsSyncableMap.containsKey(key) ? 1 : 0;
            } else {
                return -1;
            }
        }
    }

    private static String createKey(Account account, String authority) {
        return account.name + "@@@" + account.type + "@@@" + authority;
    }

    private void notifyObservers() {
        if (mDisableObserverNotifications) return;
        synchronized (mObservers) {
            mPendingObserverCount = new Semaphore(1 - mObservers.size());
            for (AsyncSyncStatusObserver observer : mObservers) {
                observer.notifyObserverAsync(mPendingObserverCount);
            }
        }
    }

    /**
     * Blocks until the last notification has been issued to all registered observers.
     * Note that if an observer is removed while a notification is being handled this can
     * fail to return correctly.
     *
     * @throws InterruptedException
     */
    public void waitForLastNotificationCompleted() throws InterruptedException {
        Assert.assertTrue("Timed out waiting for notifications to complete.",
                mPendingObserverCount.tryAcquire(5, TimeUnit.SECONDS));
    }

    public void disableObserverNotifications() {
        mDisableObserverNotifications = true;
    }

    private static class AsyncSyncStatusObserver {

        private final SyncStatusObserver mSyncStatusObserver;

        private AsyncSyncStatusObserver(SyncStatusObserver syncStatusObserver) {
            mSyncStatusObserver = syncStatusObserver;
        }

        private void notifyObserverAsync(final Semaphore pendingObserverCount) {
            if (ThreadUtils.runningOnUiThread()) {
                new AsyncTask<Void, Void, Void>() {
                    @Override
                    protected Void doInBackground(Void... params) {
                        mSyncStatusObserver.onStatusChanged(
                                ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Void result) {
                        pendingObserverCount.release();
                    }
                }.execute();
            } else {
                mSyncStatusObserver.onStatusChanged(
                        ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
                pendingObserverCount.release();
            }
        }
    }
}

/* [<][>][^][v][top][bottom][index][help] */