1// Copyright 2012 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.notifier.signin;
6
7import android.accounts.Account;
8import android.content.Context;
9import android.test.InstrumentationTestCase;
10import android.test.suitebuilder.annotation.SmallTest;
11
12import org.chromium.base.ThreadUtils;
13import org.chromium.base.test.util.Feature;
14import org.chromium.sync.notifier.SyncStatusHelper;
15import org.chromium.sync.notifier.SyncStatusHelper.CachedAccountSyncSettings;
16import org.chromium.sync.notifier.SyncStatusHelper.SyncSettingsChangedObserver;
17import org.chromium.sync.signin.ChromeSigninController;
18import org.chromium.sync.test.util.MockSyncContentResolverDelegate;
19
20public class SyncStatusHelperTest extends InstrumentationTestCase {
21
22    private static class CountingMockSyncContentResolverDelegate
23            extends MockSyncContentResolverDelegate {
24        private int mGetMasterSyncAutomaticallyCalls;
25        private int mGetSyncAutomaticallyCalls;
26        private int mGetIsSyncableCalls;
27        private int mSetIsSyncableCalls;
28        private int mSetSyncAutomaticallyCalls;
29
30        @Override
31        public boolean getMasterSyncAutomatically() {
32            mGetMasterSyncAutomaticallyCalls++;
33            return super.getMasterSyncAutomatically();
34        }
35
36        @Override
37        public boolean getSyncAutomatically(Account account, String authority) {
38            mGetSyncAutomaticallyCalls++;
39            return super.getSyncAutomatically(account, authority);
40        }
41
42        @Override
43        public int getIsSyncable(Account account, String authority) {
44            mGetIsSyncableCalls++;
45            return super.getIsSyncable(account, authority);
46        }
47
48        @Override
49        public void setIsSyncable(Account account, String authority, int syncable) {
50            mSetIsSyncableCalls++;
51            super.setIsSyncable(account, authority, syncable);
52        }
53
54        @Override
55        public void setSyncAutomatically(Account account, String authority, boolean sync) {
56            mSetSyncAutomaticallyCalls++;
57            super.setSyncAutomatically(account, authority, sync);
58        }
59    }
60
61    private static class CountingCachedAccountSyncSettings extends CachedAccountSyncSettings {
62        private int mUpdateSyncSettingsForAccountInternalCalls;
63        private int mSetIsSyncableInternalCalls;
64        private int mSetSyncAutomaticallyInternalCalls;
65
66        public CountingCachedAccountSyncSettings(String contractAuthority,
67                MockSyncContentResolverDelegate contentResolverWrapper) {
68            super(contractAuthority, contentResolverWrapper);
69        }
70
71        @Override
72        protected void updateSyncSettingsForAccountInternal(Account account) {
73            mUpdateSyncSettingsForAccountInternalCalls++;
74            super.updateSyncSettingsForAccountInternal(account);
75        }
76
77        @Override
78        protected void setIsSyncableInternal(Account account) {
79            mSetIsSyncableInternalCalls++;
80            super.setIsSyncableInternal(account);
81        }
82
83        @Override
84        protected void setSyncAutomaticallyInternal(Account account, boolean value) {
85            mSetSyncAutomaticallyInternalCalls++;
86            super.setSyncAutomaticallyInternal(account, value);
87        }
88
89        public void resetCount() {
90            mUpdateSyncSettingsForAccountInternalCalls = 0;
91        }
92    }
93
94    private static class MockSyncSettingsObserver implements SyncSettingsChangedObserver {
95        private boolean mReceivedNotification;
96
97        public void clearNotification() {
98            mReceivedNotification = false;
99        }
100
101        public boolean didReceiveNotification() {
102            return mReceivedNotification;
103        }
104
105        @Override
106        public void syncSettingsChanged() {
107            mReceivedNotification = true;
108        }
109    }
110
111    private SyncStatusHelper mHelper;
112    private CountingMockSyncContentResolverDelegate mSyncContentResolverDelegate;
113    private String mAuthority;
114    private Account mTestAccount;
115    private Account mAlternateTestAccount;
116    private CountingCachedAccountSyncSettings mCachedAccountSyncSettings;
117    private MockSyncSettingsObserver mSyncSettingsObserver;
118
119    @Override
120    protected void setUp() throws Exception {
121        mSyncContentResolverDelegate = new CountingMockSyncContentResolverDelegate();
122        Context context = getInstrumentation().getTargetContext();
123        mCachedAccountSyncSettings = new CountingCachedAccountSyncSettings(
124                context.getPackageName(), mSyncContentResolverDelegate);
125        SyncStatusHelper.overrideSyncStatusHelperForTests(
126                context, mSyncContentResolverDelegate, mCachedAccountSyncSettings);
127        mHelper = SyncStatusHelper.get(getInstrumentation().getTargetContext());
128        // Need to set the signed in account name to ensure that sync settings notifications
129        // update the right account.
130        ChromeSigninController.get(
131                getInstrumentation().getTargetContext()).setSignedInAccountName(
132                        "account@example.com");
133        mAuthority = SyncStatusHelper.get(getInstrumentation().getTargetContext())
134                .getContractAuthority();
135        mTestAccount = new Account("account@example.com", "com.google");
136        mAlternateTestAccount = new Account("alternateAccount@example.com", "com.google");
137
138        mSyncSettingsObserver = new MockSyncSettingsObserver();
139        mHelper.registerSyncSettingsChangedObserver(mSyncSettingsObserver);
140
141        super.setUp();
142    }
143
144    @SmallTest
145    @Feature({"Sync"})
146    public void testToggleMasterSyncAutomaticallyFromSettings() throws InterruptedException {
147        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
148        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
149        assertTrue("master sync should be set", mHelper.isMasterSyncAutomaticallyEnabled());
150
151        mSyncContentResolverDelegate.setMasterSyncAutomatically(false);
152        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
153        assertFalse("master sync should be unset", mHelper.isMasterSyncAutomaticallyEnabled());
154    }
155
156    @SmallTest
157    @Feature({"Sync"})
158    public void testToggleAccountSyncFromSettings() throws InterruptedException {
159        // Turn on syncability.
160        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
161        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
162
163        // First sync
164        mSyncContentResolverDelegate.setIsSyncable(mTestAccount, mAuthority, 1);
165        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
166        mSyncContentResolverDelegate.setSyncAutomatically(mTestAccount, mAuthority, true);
167        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
168        assertTrue("sync should be set", mHelper.isSyncEnabled(mTestAccount));
169        assertTrue("sync should be set for chrome app",
170                mHelper.isSyncEnabledForChrome(mTestAccount));
171
172        // Disable sync automatically for the app
173        mSyncContentResolverDelegate.setSyncAutomatically(mTestAccount, mAuthority, false);
174        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
175        assertFalse("sync should be unset", mHelper.isSyncEnabled(mTestAccount));
176        assertFalse("sync should be unset for chrome app",
177                mHelper.isSyncEnabledForChrome(mTestAccount));
178
179        // Re-enable sync
180        mSyncContentResolverDelegate.setSyncAutomatically(mTestAccount, mAuthority, true);
181        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
182        assertTrue("sync should be re-enabled", mHelper.isSyncEnabled(mTestAccount));
183        assertTrue("sync should be unset for chrome app",
184                mHelper.isSyncEnabledForChrome(mTestAccount));
185
186        // Disabled from master sync
187        mSyncContentResolverDelegate.setMasterSyncAutomatically(false);
188        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
189        assertFalse("sync should be disabled due to master sync",
190                mHelper.isSyncEnabled(mTestAccount));
191        assertTrue("sync should be set for chrome app",
192                mHelper.isSyncEnabledForChrome(mTestAccount));
193    }
194
195    @SmallTest
196    @Feature({"Sync"})
197    public void testToggleAccountSyncFromApplication() throws InterruptedException {
198        // Turn on syncability.
199        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
200        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
201
202        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
203            @Override
204            public void run() {
205                mHelper.enableAndroidSync(mTestAccount);
206            }
207        });
208        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
209        assertTrue("account should be synced", mHelper.isSyncEnabled(mTestAccount));
210
211        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
212            @Override
213            public void run() {
214                mHelper.disableAndroidSync(mTestAccount);
215            }
216        });
217        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
218        assertFalse("account should not be synced", mHelper.isSyncEnabled(mTestAccount));
219    }
220
221    @SmallTest
222    @Feature({"Sync"})
223    public void testToggleSyncabilityForMultipleAccounts() throws InterruptedException {
224        // Turn on syncability.
225        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
226        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
227
228        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
229            @Override
230            public void run() {
231                mHelper.enableAndroidSync(mTestAccount);
232            }
233        });
234        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
235        assertTrue("account should be synced", mHelper.isSyncEnabled(mTestAccount));
236
237        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
238            @Override
239            public void run() {
240                mHelper.enableAndroidSync(mAlternateTestAccount);
241            }
242        });
243        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
244        assertTrue("alternate account should be synced",
245                mHelper.isSyncEnabled(mAlternateTestAccount));
246
247        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
248            @Override
249            public void run() {
250                mHelper.disableAndroidSync(mAlternateTestAccount);
251            }
252        });
253        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
254        assertFalse("alternate account should not be synced",
255                mHelper.isSyncEnabled(mAlternateTestAccount));
256        assertTrue("account should still be synced", mHelper.isSyncEnabled(mTestAccount));
257
258        // Ensure we don't erroneously re-use cached data.
259        assertFalse("null account should not be synced", mHelper.isSyncEnabled(null));
260    }
261
262    @SmallTest
263    @Feature({"Sync"})
264    public void testSyncSettingsCaching() throws InterruptedException {
265        // Turn on syncability.
266        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
267        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
268
269        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
270            @Override
271            public void run() {
272                mHelper.enableAndroidSync(mTestAccount);
273            }
274        });
275        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
276        assertTrue("account should be synced", mHelper.isSyncEnabled(mTestAccount));
277
278        int masterSyncAutomaticallyCalls =
279                mSyncContentResolverDelegate.mGetMasterSyncAutomaticallyCalls;
280        int isSyncableCalls = mSyncContentResolverDelegate.mGetIsSyncableCalls;
281        int getSyncAutomaticallyAcalls = mSyncContentResolverDelegate.mGetSyncAutomaticallyCalls;
282
283        // Do a bunch of reads.
284        mHelper.isMasterSyncAutomaticallyEnabled();
285        mHelper.isSyncEnabled();
286        mHelper.isSyncEnabled(mTestAccount);
287        mHelper.isSyncEnabledForChrome(mTestAccount);
288
289        // Ensure values were read from cache.
290        assertEquals(masterSyncAutomaticallyCalls,
291                mSyncContentResolverDelegate.mGetMasterSyncAutomaticallyCalls);
292        assertEquals(isSyncableCalls, mSyncContentResolverDelegate.mGetIsSyncableCalls);
293        assertEquals(getSyncAutomaticallyAcalls,
294                mSyncContentResolverDelegate.mGetSyncAutomaticallyCalls);
295
296        // Do a bunch of reads for alternate account.
297        mHelper.isMasterSyncAutomaticallyEnabled();
298        mHelper.isSyncEnabled(mAlternateTestAccount);
299        mHelper.isSyncEnabledForChrome(mAlternateTestAccount);
300
301        // Ensure master sync was cached but others are fetched once.
302        assertEquals(masterSyncAutomaticallyCalls,
303                mSyncContentResolverDelegate.mGetMasterSyncAutomaticallyCalls);
304        assertEquals(isSyncableCalls + 1, mSyncContentResolverDelegate.mGetIsSyncableCalls);
305        assertEquals(getSyncAutomaticallyAcalls + 1,
306                mSyncContentResolverDelegate.mGetSyncAutomaticallyCalls);
307    }
308
309    @SmallTest
310    @Feature({"Sync"})
311    public void testGetContractAuthority() throws Exception {
312        assertEquals("The contract authority should be the package name.",
313                getInstrumentation().getTargetContext().getPackageName(),
314                mHelper.getContractAuthority());
315    }
316
317    @SmallTest
318    @Feature({"Sync"})
319    public void testCachedAccountSyncSettingsExitEarly() throws InterruptedException {
320        mSyncContentResolverDelegate.disableObserverNotifications();
321
322        mCachedAccountSyncSettings.updateSyncSettingsForAccount(null);
323        assertTrue("Update sync settings failed to exit early", mCachedAccountSyncSettings.
324                mUpdateSyncSettingsForAccountInternalCalls == 0);
325
326        mCachedAccountSyncSettings.updateSyncSettingsForAccount(mTestAccount);
327        assertTrue("Update sync settings should not have exited early", mCachedAccountSyncSettings.
328                mUpdateSyncSettingsForAccountInternalCalls == 1);
329
330        mCachedAccountSyncSettings.setIsSyncable(mTestAccount);
331        assertTrue("setIsSyncable should not have exited early",
332                mCachedAccountSyncSettings.mSetIsSyncableInternalCalls == 1);
333
334        mCachedAccountSyncSettings.setIsSyncable(mTestAccount);
335        assertTrue("setIsSyncable failed to exit early", mCachedAccountSyncSettings.
336                mSetIsSyncableInternalCalls == 1);
337
338        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, true);
339        assertTrue("setSyncAutomatically should not have to exited early",
340                mCachedAccountSyncSettings.mSetSyncAutomaticallyInternalCalls == 1);
341
342        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, true);
343        assertTrue("setSyncAutomatically failed to exit early",
344                mCachedAccountSyncSettings.mSetSyncAutomaticallyInternalCalls == 1);
345
346        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, false);
347        assertTrue("setSyncAutomatically should not have to exited early",
348                mCachedAccountSyncSettings.mSetSyncAutomaticallyInternalCalls == 2);
349
350        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, false);
351        assertTrue("setSyncAutomatically failed to exit early",
352                mCachedAccountSyncSettings.mSetSyncAutomaticallyInternalCalls == 2);
353    }
354
355    @SmallTest
356    @Feature({"Sync"})
357    public void testCachedAccountSyncSettingsDidUpdate() throws InterruptedException {
358        // Since we're just testing the cache we disable observer notifications to prevent
359        // notifications to SyncStatusHelper from mutating it.
360        mSyncContentResolverDelegate.disableObserverNotifications();
361
362        mCachedAccountSyncSettings.clearUpdateStatus();
363        mCachedAccountSyncSettings.getSyncAutomatically(mTestAccount);
364        assertTrue("getSyncAutomatically on un-populated cache failed to update DidUpdate flag",
365                mCachedAccountSyncSettings.getDidUpdateStatus());
366
367        mCachedAccountSyncSettings.clearUpdateStatus();
368        mCachedAccountSyncSettings.getSyncAutomatically(mTestAccount);
369        assertFalse("getSyncAutomatically on populated cache updated DidUpdate flag",
370                mCachedAccountSyncSettings.getDidUpdateStatus());
371
372        mCachedAccountSyncSettings.updateSyncSettingsForAccount(mAlternateTestAccount);
373        assertTrue("updateSyncSettingsForAccount failed to update DidUpdate flag",
374                mCachedAccountSyncSettings.getDidUpdateStatus());
375
376        mCachedAccountSyncSettings.clearUpdateStatus();
377
378        mCachedAccountSyncSettings.updateSyncSettingsForAccount(mTestAccount);
379        assertTrue("updateSyncSettingsForAccount failed to update DidUpdate flag",
380                mCachedAccountSyncSettings.getDidUpdateStatus());
381
382        mCachedAccountSyncSettings.clearUpdateStatus();
383
384        mCachedAccountSyncSettings.updateSyncSettingsForAccount(mTestAccount);
385        assertFalse("updateSyncSettingsForAccount updated DidUpdate flag",
386                mCachedAccountSyncSettings.getDidUpdateStatus());
387
388        mCachedAccountSyncSettings.clearUpdateStatus();
389        mCachedAccountSyncSettings.setIsSyncable(mTestAccount);
390        assertTrue("setIsSyncable failed to update DidUpdate flag",
391                mCachedAccountSyncSettings.getDidUpdateStatus());
392
393        mCachedAccountSyncSettings.clearUpdateStatus();
394        mCachedAccountSyncSettings.setIsSyncable(mTestAccount);
395        assertFalse("setIsSyncable updated DidUpdate flag",
396                mCachedAccountSyncSettings.getDidUpdateStatus());
397
398        mCachedAccountSyncSettings.clearUpdateStatus();
399        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, true);
400        assertTrue("setSyncAutomatically failed to update DidUpdate flag",
401                mCachedAccountSyncSettings.getDidUpdateStatus());
402
403        mCachedAccountSyncSettings.clearUpdateStatus();
404        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, true);
405        assertFalse("setSyncAutomatically updated DidUpdate flag",
406                mCachedAccountSyncSettings.getDidUpdateStatus());
407
408        mCachedAccountSyncSettings.clearUpdateStatus();
409        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, false);
410        assertTrue("setSyncAutomatically failed to update DidUpdate flag",
411                mCachedAccountSyncSettings.getDidUpdateStatus());
412
413        mCachedAccountSyncSettings.clearUpdateStatus();
414        mCachedAccountSyncSettings.setSyncAutomatically(mTestAccount, false);
415        assertFalse("setSyncAutomatically updated DidUpdate flag",
416                mCachedAccountSyncSettings.getDidUpdateStatus());
417    }
418
419    @SmallTest
420    @Feature({"Sync"})
421    public void testSyncStatusHelperPostsNotifications() throws InterruptedException {
422        // Turn on syncability.
423        mSyncContentResolverDelegate.setMasterSyncAutomatically(true);
424        mSyncContentResolverDelegate.waitForLastNotificationCompleted();
425
426        mSyncSettingsObserver.clearNotification();
427        mHelper.isSyncEnabled(mAlternateTestAccount);
428        assertTrue("isSyncEnabled on wrongly populated cache did not trigger observers",
429                mSyncSettingsObserver.didReceiveNotification());
430
431        mSyncSettingsObserver.clearNotification();
432        mHelper.isSyncEnabled(mTestAccount);
433        assertTrue("isSyncEnabled on wrongly populated cache did not trigger observers",
434                mSyncSettingsObserver.didReceiveNotification());
435
436        mSyncSettingsObserver.clearNotification();
437        mHelper.enableAndroidSync(mTestAccount);
438        assertTrue("enableAndroidSync did not trigger observers",
439                mSyncSettingsObserver.didReceiveNotification());
440
441        mSyncSettingsObserver.clearNotification();
442        mHelper.enableAndroidSync(mTestAccount);
443        assertFalse("enableAndroidSync triggered observers",
444                mSyncSettingsObserver.didReceiveNotification());
445
446        mSyncSettingsObserver.clearNotification();
447        mHelper.disableAndroidSync(mTestAccount);
448        assertTrue("disableAndroidSync did not trigger observers",
449                mSyncSettingsObserver.didReceiveNotification());
450
451        mSyncSettingsObserver.clearNotification();
452        mHelper.disableAndroidSync(mTestAccount);
453        assertFalse("disableAndroidSync triggered observers",
454                mSyncSettingsObserver.didReceiveNotification());
455    }
456}
457