1 // Copyright 2015 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.
5 package org
.chromium
.sync
;
7 import android
.accounts
.Account
;
8 import android
.content
.ContentResolver
;
9 import android
.content
.Context
;
10 import android
.content
.SyncStatusObserver
;
11 import android
.os
.StrictMode
;
13 import org
.chromium
.base
.ObserverList
;
14 import org
.chromium
.base
.VisibleForTesting
;
15 import org
.chromium
.sync
.signin
.AccountManagerHelper
;
17 import javax
.annotation
.concurrent
.ThreadSafe
;
20 * A helper class to handle the current status of sync for Chrome in Android settings.
22 * It also provides an observer to be used whenever Android sync settings change.
24 * This class is a collection of static methods so that no references to its object can be
25 * stored. This is important because tests need to be able to overwrite the object with a
26 * mock content resolver and know that no references to the old one are cached.
28 * This class must be initialized via updateAccount() on startup if the user is signed in.
31 public class AndroidSyncSettings
{
33 public static final String TAG
= "AndroidSyncSettings";
36 * Lock for ensuring singleton instantiation across threads.
38 private static final Object CLASS_LOCK
= new Object();
40 private static AndroidSyncSettings sInstance
;
42 private final Object mLock
= new Object();
44 private final String mContractAuthority
;
46 private final Context mApplicationContext
;
48 private final SyncContentResolverDelegate mSyncContentResolverDelegate
;
50 private Account mAccount
= null;
52 private boolean mIsSyncable
= false;
54 private boolean mChromeSyncEnabled
= false;
56 private boolean mMasterSyncEnabled
= false;
58 private final ObserverList
<AndroidSyncSettingsObserver
> mObservers
=
59 new ObserverList
<AndroidSyncSettingsObserver
>();
62 * Provides notifications when Android sync settings have changed.
64 public interface AndroidSyncSettingsObserver
{
65 public void androidSyncSettingsChanged();
68 private static void ensureInitialized(Context context
) {
69 synchronized (CLASS_LOCK
) {
70 if (sInstance
== null) {
71 SyncContentResolverDelegate contentResolver
=
72 new SystemSyncContentResolverDelegate();
73 sInstance
= new AndroidSyncSettings(context
, contentResolver
);
79 public static void overrideForTests(Context context
,
80 SyncContentResolverDelegate contentResolver
) {
81 synchronized (CLASS_LOCK
) {
82 sInstance
= new AndroidSyncSettings(context
, contentResolver
);
87 * @param context the context the ApplicationContext will be retrieved from.
88 * @param syncContentResolverDelegate an implementation of {@link SyncContentResolverDelegate}.
90 private AndroidSyncSettings(Context context
,
91 SyncContentResolverDelegate syncContentResolverDelegate
) {
92 mApplicationContext
= context
.getApplicationContext();
93 mSyncContentResolverDelegate
= syncContentResolverDelegate
;
94 mContractAuthority
= getContractAuthority();
96 updateCachedSettings();
98 mSyncContentResolverDelegate
.addStatusChangeListener(
99 ContentResolver
.SYNC_OBSERVER_TYPE_SETTINGS
,
100 new AndroidSyncSettingsChangedObserver());
104 * Checks whether sync is currently enabled from Chrome for the currently signed in account.
106 * It checks both the master sync for the device, and Chrome sync setting for the given account.
107 * If no user is currently signed in it returns false.
109 * @return true if sync is on, false otherwise
111 public static boolean isSyncEnabled(Context context
) {
112 ensureInitialized(context
);
113 return sInstance
.mMasterSyncEnabled
&& sInstance
.mChromeSyncEnabled
;
117 * Checks whether sync is currently enabled from Chrome for a given account.
119 * It checks only Chrome sync setting for the given account,
120 * and ignores the master sync setting.
122 * @return true if sync is on, false otherwise
124 public static boolean isChromeSyncEnabled(Context context
) {
125 ensureInitialized(context
);
126 return sInstance
.mChromeSyncEnabled
;
130 * Checks whether the master sync flag for Android is currently enabled.
132 public static boolean isMasterSyncEnabled(Context context
) {
133 ensureInitialized(context
);
134 return sInstance
.mMasterSyncEnabled
;
138 * Make sure Chrome is syncable, and enable sync.
140 public static void enableChromeSync(Context context
) {
141 ensureInitialized(context
);
142 sInstance
.setChromeSyncEnabled(true);
146 * Disables Android Chrome sync
148 public static void disableChromeSync(Context context
) {
149 ensureInitialized(context
);
150 sInstance
.setChromeSyncEnabled(false);
154 * Must be called when a new account is signed in.
156 public static void updateAccount(Context context
, Account account
) {
157 ensureInitialized(context
);
158 synchronized (sInstance
.mLock
) {
159 sInstance
.mAccount
= account
;
160 sInstance
.updateSyncability();
162 if (sInstance
.updateCachedSettings()) {
163 sInstance
.notifyObservers();
168 * Returns the contract authority to use when requesting sync.
170 public static String
getContractAuthority(Context context
) {
171 ensureInitialized(context
);
172 return sInstance
.getContractAuthority();
176 * Add a new AndroidSyncSettingsObserver.
178 public static void registerObserver(Context context
, AndroidSyncSettingsObserver observer
) {
179 ensureInitialized(context
);
180 synchronized (sInstance
.mLock
) {
181 sInstance
.mObservers
.addObserver(observer
);
186 * Remove an AndroidSyncSettingsObserver that was previously added.
188 public static void unregisterObserver(Context context
, AndroidSyncSettingsObserver observer
) {
189 ensureInitialized(context
);
190 synchronized (sInstance
.mLock
) {
191 sInstance
.mObservers
.removeObserver(observer
);
195 private void setChromeSyncEnabled(boolean value
) {
196 synchronized (mLock
) {
198 if (value
== mChromeSyncEnabled
|| mAccount
== null) return;
199 mChromeSyncEnabled
= value
;
201 StrictMode
.ThreadPolicy oldPolicy
= StrictMode
.allowThreadDiskWrites();
202 mSyncContentResolverDelegate
.setSyncAutomatically(mAccount
, mContractAuthority
, value
);
203 StrictMode
.setThreadPolicy(oldPolicy
);
209 * Ensure Chrome is registered with the Android Sync Manager iff signed in.
211 * This is what causes the "Chrome" option to appear in Settings -> Accounts -> Sync .
212 * This function must be called within a synchronized block.
214 private void updateSyncability() {
215 boolean shouldBeSyncable
= mAccount
!= null;
216 if (mIsSyncable
== shouldBeSyncable
) return;
218 mIsSyncable
= shouldBeSyncable
;
220 StrictMode
.ThreadPolicy oldPolicy
= StrictMode
.allowThreadDiskWrites();
221 // Make account syncable if there is one.
222 if (shouldBeSyncable
) {
223 mSyncContentResolverDelegate
.setIsSyncable(mAccount
, mContractAuthority
, 1);
226 // Disable the syncability of Chrome for all other accounts. Don't use
227 // our cache as we're touching many accounts that aren't signed in, so this saves
228 // extra calls to Android sync configuration.
229 Account
[] googleAccounts
= AccountManagerHelper
.get(mApplicationContext
)
230 .getGoogleAccounts();
231 for (Account accountToSetNotSyncable
: googleAccounts
) {
232 if (!accountToSetNotSyncable
.equals(mAccount
)
233 && mSyncContentResolverDelegate
.getIsSyncable(
234 accountToSetNotSyncable
, mContractAuthority
) > 0) {
235 mSyncContentResolverDelegate
.setIsSyncable(accountToSetNotSyncable
,
236 mContractAuthority
, 0);
239 StrictMode
.setThreadPolicy(oldPolicy
);
243 * Helper class to be used by observers whenever sync settings change.
245 * To register the observer, call AndroidSyncSettings.registerObserver(...).
247 private class AndroidSyncSettingsChangedObserver
implements SyncStatusObserver
{
249 public void onStatusChanged(int which
) {
250 if (which
== ContentResolver
.SYNC_OBSERVER_TYPE_SETTINGS
) {
251 // Sync settings have changed; update our cached values.
252 if (updateCachedSettings()) {
253 // If something actually changed, tell our observers.
261 * Update the three cached settings from the content resolver.
263 * @return Whether either chromeSyncEnabled or masterSyncEnabled changed.
265 private boolean updateCachedSettings() {
266 synchronized (mLock
) {
267 boolean oldChromeSyncEnabled
= mChromeSyncEnabled
;
268 boolean oldMasterSyncEnabled
= mMasterSyncEnabled
;
270 StrictMode
.ThreadPolicy oldPolicy
= StrictMode
.allowThreadDiskWrites();
271 if (mAccount
!= null) {
272 mIsSyncable
= mSyncContentResolverDelegate
.getIsSyncable(
273 mAccount
, mContractAuthority
) == 1;
274 mChromeSyncEnabled
= mSyncContentResolverDelegate
.getSyncAutomatically(
275 mAccount
, mContractAuthority
);
278 mChromeSyncEnabled
= false;
280 mMasterSyncEnabled
= mSyncContentResolverDelegate
.getMasterSyncAutomatically();
281 StrictMode
.setThreadPolicy(oldPolicy
);
283 return oldChromeSyncEnabled
!= mChromeSyncEnabled
284 || oldMasterSyncEnabled
!= mMasterSyncEnabled
;
288 private void notifyObservers() {
289 for (AndroidSyncSettingsObserver observer
: mObservers
) {
290 observer
.androidSyncSettingsChanged();
294 // TODO(maxbogue): make private once downstream uses are removed.
296 public String
getContractAuthority() {
297 return mApplicationContext
.getPackageName();
300 // Deprecated section; to be removed once downstream no longer uses them.
303 public static AndroidSyncSettings
get(Context context
) {
304 ensureInitialized(context
);
309 public boolean isSyncEnabled() {
310 return mMasterSyncEnabled
&& mChromeSyncEnabled
;
314 public boolean isChromeSyncEnabled() {
315 return mChromeSyncEnabled
;
319 public boolean isMasterSyncEnabled() {
320 return mMasterSyncEnabled
;
324 public void enableChromeSync() {
325 setChromeSyncEnabled(true);
329 public void disableChromeSync() {
330 setChromeSyncEnabled(false);
334 public void updateAccount(Account account
) {
335 synchronized (mLock
) {
339 if (updateCachedSettings()) {
345 public void registerObserver(AndroidSyncSettingsObserver observer
) {
346 synchronized (mLock
) {
347 mObservers
.addObserver(observer
);
352 public void unregisterObserver(AndroidSyncSettingsObserver observer
) {
353 synchronized (mLock
) {
354 mObservers
.removeObserver(observer
);