1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.sync.test.util; 6 7 8import android.accounts.Account; 9import android.content.ContentResolver; 10import android.content.SyncStatusObserver; 11import android.os.AsyncTask; 12 13import junit.framework.Assert; 14 15import org.chromium.base.ThreadUtils; 16import org.chromium.sync.notifier.SyncContentResolverDelegate; 17 18import java.util.HashMap; 19import java.util.HashSet; 20import java.util.Map; 21import java.util.Set; 22import java.util.concurrent.Semaphore; 23import java.util.concurrent.TimeUnit; 24 25 26/** 27 * Mock implementation of the {@link SyncContentResolverDelegate}. 28 * 29 * This implementation only supports status change listeners for the type 30 * SYNC_OBSERVER_TYPE_SETTINGS. 31 */ 32public class MockSyncContentResolverDelegate implements SyncContentResolverDelegate { 33 34 private final Set<String> mSyncAutomaticallySet; 35 private final Map<String, Boolean> mIsSyncableMap; 36 private final Object mSyncableMapLock = new Object(); 37 38 private final Set<AsyncSyncStatusObserver> mObservers; 39 40 private boolean mMasterSyncAutomatically; 41 private boolean mDisableObserverNotifications; 42 43 private Semaphore mPendingObserverCount; 44 45 public MockSyncContentResolverDelegate() { 46 mSyncAutomaticallySet = new HashSet<String>(); 47 mIsSyncableMap = new HashMap<String, Boolean>(); 48 mObservers = new HashSet<AsyncSyncStatusObserver>(); 49 } 50 51 @Override 52 public Object addStatusChangeListener(int mask, SyncStatusObserver callback) { 53 if (mask != ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) { 54 throw new IllegalArgumentException("This implementation only supports " 55 + "ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS as the mask"); 56 } 57 AsyncSyncStatusObserver asyncSyncStatusObserver = new AsyncSyncStatusObserver(callback); 58 synchronized (mObservers) { 59 mObservers.add(asyncSyncStatusObserver); 60 } 61 return asyncSyncStatusObserver; 62 } 63 64 @Override 65 public void removeStatusChangeListener(Object handle) { 66 synchronized (mObservers) { 67 mObservers.remove(handle); 68 } 69 } 70 71 @Override 72 public void setMasterSyncAutomatically(boolean sync) { 73 if (mMasterSyncAutomatically == sync) return; 74 75 mMasterSyncAutomatically = sync; 76 notifyObservers(); 77 } 78 79 @Override 80 public boolean getMasterSyncAutomatically() { 81 return mMasterSyncAutomatically; 82 } 83 84 @Override 85 public boolean getSyncAutomatically(Account account, String authority) { 86 String key = createKey(account, authority); 87 synchronized (mSyncableMapLock) { 88 return mSyncAutomaticallySet.contains(key); 89 } 90 } 91 92 @Override 93 public void setSyncAutomatically(Account account, String authority, boolean sync) { 94 String key = createKey(account, authority); 95 synchronized (mSyncableMapLock) { 96 if (!mIsSyncableMap.containsKey(key) || !mIsSyncableMap.get(key)) { 97 throw new IllegalArgumentException("Account " + account + 98 " is not syncable for authority " + authority + 99 ". Can not set sync state to " + sync); 100 } 101 if (sync) { 102 mSyncAutomaticallySet.add(key); 103 } else if (mSyncAutomaticallySet.contains(key)) { 104 mSyncAutomaticallySet.remove(key); 105 } 106 } 107 notifyObservers(); 108 } 109 110 @Override 111 public void setIsSyncable(Account account, String authority, int syncable) { 112 String key = createKey(account, authority); 113 114 synchronized (mSyncableMapLock) { 115 switch (syncable) { 116 case 0: 117 if (mSyncAutomaticallySet.contains(key)) { 118 mSyncAutomaticallySet.remove(key); 119 } 120 121 mIsSyncableMap.put(key, false); 122 break; 123 case 1: 124 mIsSyncableMap.put(key, true); 125 break; 126 case -1: 127 if (mIsSyncableMap.containsKey(key)) { 128 mIsSyncableMap.remove(key); 129 } 130 break; 131 default: 132 throw new IllegalArgumentException("Unable to understand syncable argument: " + 133 syncable); 134 } 135 } 136 notifyObservers(); 137 } 138 139 @Override 140 public int getIsSyncable(Account account, String authority) { 141 String key = createKey(account, authority); 142 synchronized (mSyncableMapLock) { 143 if (mIsSyncableMap.containsKey(key)) { 144 return mIsSyncableMap.containsKey(key) ? 1 : 0; 145 } else { 146 return -1; 147 } 148 } 149 } 150 151 private static String createKey(Account account, String authority) { 152 return account.name + "@@@" + account.type + "@@@" + authority; 153 } 154 155 private void notifyObservers() { 156 if (mDisableObserverNotifications) return; 157 synchronized (mObservers) { 158 mPendingObserverCount = new Semaphore(1 - mObservers.size()); 159 for (AsyncSyncStatusObserver observer : mObservers) { 160 observer.notifyObserverAsync(mPendingObserverCount); 161 } 162 } 163 } 164 165 /** 166 * Blocks until the last notification has been issued to all registered observers. 167 * Note that if an observer is removed while a notification is being handled this can 168 * fail to return correctly. 169 * 170 * @throws InterruptedException 171 */ 172 public void waitForLastNotificationCompleted() throws InterruptedException { 173 Assert.assertTrue("Timed out waiting for notifications to complete.", 174 mPendingObserverCount.tryAcquire(5, TimeUnit.SECONDS)); 175 } 176 177 public void disableObserverNotifications() { 178 mDisableObserverNotifications = true; 179 } 180 181 private static class AsyncSyncStatusObserver { 182 183 private final SyncStatusObserver mSyncStatusObserver; 184 185 private AsyncSyncStatusObserver(SyncStatusObserver syncStatusObserver) { 186 mSyncStatusObserver = syncStatusObserver; 187 } 188 189 private void notifyObserverAsync(final Semaphore pendingObserverCount) { 190 if (ThreadUtils.runningOnUiThread()) { 191 new AsyncTask<Void, Void, Void>() { 192 @Override 193 protected Void doInBackground(Void... params) { 194 mSyncStatusObserver.onStatusChanged( 195 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 196 return null; 197 } 198 199 @Override 200 protected void onPostExecute(Void result) { 201 pendingObserverCount.release(); 202 } 203 }.execute(); 204 } else { 205 mSyncStatusObserver.onStatusChanged( 206 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 207 pendingObserverCount.release(); 208 } 209 } 210 } 211} 212