OLD | NEW |
| (Empty) |
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. | |
4 | |
5 package org.chromium.sync; | |
6 | |
7 import android.accounts.Account; | |
8 import android.content.ContentResolver; | |
9 import android.content.Context; | |
10 import android.content.SyncStatusObserver; | |
11 import android.os.Bundle; | |
12 import android.os.StrictMode; | |
13 | |
14 import org.chromium.base.Callback; | |
15 import org.chromium.base.ObserverList; | |
16 import org.chromium.base.VisibleForTesting; | |
17 import org.chromium.sync.signin.AccountManagerHelper; | |
18 | |
19 import javax.annotation.concurrent.ThreadSafe; | |
20 | |
21 /** | |
22 * A helper class to handle the current status of sync for Chrome in Android set
tings. | |
23 * | |
24 * It also provides an observer to be used whenever Android sync settings change
. | |
25 * | |
26 * This class is a collection of static methods so that no references to its obj
ect can be | |
27 * stored. This is important because tests need to be able to overwrite the obje
ct with a | |
28 * mock content resolver and know that no references to the old one are cached. | |
29 * | |
30 * This class must be initialized via updateAccount() on startup if the user is
signed in. | |
31 */ | |
32 @ThreadSafe | |
33 public class AndroidSyncSettings { | |
34 | |
35 public static final String TAG = "AndroidSyncSettings"; | |
36 | |
37 /** | |
38 * Lock for ensuring singleton instantiation across threads. | |
39 */ | |
40 private static final Object CLASS_LOCK = new Object(); | |
41 | |
42 private static AndroidSyncSettings sInstance; | |
43 | |
44 private final Object mLock = new Object(); | |
45 | |
46 private final String mContractAuthority; | |
47 | |
48 private final Context mApplicationContext; | |
49 | |
50 private final SyncContentResolverDelegate mSyncContentResolverDelegate; | |
51 | |
52 private Account mAccount = null; | |
53 | |
54 private boolean mIsSyncable = false; | |
55 | |
56 private boolean mChromeSyncEnabled = false; | |
57 | |
58 private boolean mMasterSyncEnabled = false; | |
59 | |
60 private final ObserverList<AndroidSyncSettingsObserver> mObservers = | |
61 new ObserverList<AndroidSyncSettingsObserver>(); | |
62 | |
63 /** | |
64 * Provides notifications when Android sync settings have changed. | |
65 */ | |
66 public interface AndroidSyncSettingsObserver { | |
67 public void androidSyncSettingsChanged(); | |
68 } | |
69 | |
70 private static void ensureInitialized(Context context) { | |
71 synchronized (CLASS_LOCK) { | |
72 if (sInstance == null) { | |
73 SyncContentResolverDelegate contentResolver = | |
74 new SystemSyncContentResolverDelegate(); | |
75 sInstance = new AndroidSyncSettings(context, contentResolver); | |
76 } | |
77 } | |
78 } | |
79 | |
80 @VisibleForTesting | |
81 public static void overrideForTests(Context context, | |
82 SyncContentResolverDelegate contentResolver) { | |
83 synchronized (CLASS_LOCK) { | |
84 sInstance = new AndroidSyncSettings(context, contentResolver); | |
85 } | |
86 } | |
87 | |
88 /** | |
89 * @param context the context the ApplicationContext will be retrieved from. | |
90 * @param syncContentResolverDelegate an implementation of {@link SyncConten
tResolverDelegate}. | |
91 */ | |
92 private AndroidSyncSettings(Context context, | |
93 SyncContentResolverDelegate syncContentResolverDelegate) { | |
94 mApplicationContext = context.getApplicationContext(); | |
95 mSyncContentResolverDelegate = syncContentResolverDelegate; | |
96 mContractAuthority = getContractAuthority(); | |
97 | |
98 updateCachedSettings(); | |
99 | |
100 mSyncContentResolverDelegate.addStatusChangeListener( | |
101 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, | |
102 new AndroidSyncSettingsChangedObserver()); | |
103 } | |
104 | |
105 /** | |
106 * Checks whether sync is currently enabled from Chrome for the currently si
gned in account. | |
107 * | |
108 * It checks both the master sync for the device, and Chrome sync setting fo
r the given account. | |
109 * If no user is currently signed in it returns false. | |
110 * | |
111 * @return true if sync is on, false otherwise | |
112 */ | |
113 public static boolean isSyncEnabled(Context context) { | |
114 ensureInitialized(context); | |
115 return sInstance.mMasterSyncEnabled && sInstance.mChromeSyncEnabled; | |
116 } | |
117 | |
118 /** | |
119 * Checks whether sync is currently enabled from Chrome for a given account. | |
120 * | |
121 * It checks only Chrome sync setting for the given account, | |
122 * and ignores the master sync setting. | |
123 * | |
124 * @return true if sync is on, false otherwise | |
125 */ | |
126 @VisibleForTesting | |
127 public static boolean isChromeSyncEnabled(Context context) { | |
128 ensureInitialized(context); | |
129 return sInstance.mChromeSyncEnabled; | |
130 } | |
131 | |
132 /** | |
133 * Checks whether the master sync flag for Android is currently enabled. | |
134 */ | |
135 public static boolean isMasterSyncEnabled(Context context) { | |
136 ensureInitialized(context); | |
137 return sInstance.mMasterSyncEnabled; | |
138 } | |
139 | |
140 /** | |
141 * Make sure Chrome is syncable, and enable sync. | |
142 */ | |
143 public static void enableChromeSync(Context context) { | |
144 ensureInitialized(context); | |
145 sInstance.setChromeSyncEnabled(true); | |
146 } | |
147 | |
148 /** | |
149 * Disables Android Chrome sync | |
150 */ | |
151 public static void disableChromeSync(Context context) { | |
152 ensureInitialized(context); | |
153 sInstance.setChromeSyncEnabled(false); | |
154 } | |
155 | |
156 /** | |
157 * Must be called when a new account is signed in. | |
158 */ | |
159 public static void updateAccount(Context context, Account account) { | |
160 ensureInitialized(context); | |
161 synchronized (sInstance.mLock) { | |
162 sInstance.mAccount = account; | |
163 sInstance.updateSyncability(); | |
164 } | |
165 if (sInstance.updateCachedSettings()) { | |
166 sInstance.notifyObservers(); | |
167 } | |
168 } | |
169 | |
170 /** | |
171 * Returns the contract authority to use when requesting sync. | |
172 */ | |
173 public static String getContractAuthority(Context context) { | |
174 ensureInitialized(context); | |
175 return sInstance.getContractAuthority(); | |
176 } | |
177 | |
178 /** | |
179 * Add a new AndroidSyncSettingsObserver. | |
180 */ | |
181 public static void registerObserver(Context context, AndroidSyncSettingsObse
rver observer) { | |
182 ensureInitialized(context); | |
183 synchronized (sInstance.mLock) { | |
184 sInstance.mObservers.addObserver(observer); | |
185 } | |
186 } | |
187 | |
188 /** | |
189 * Remove an AndroidSyncSettingsObserver that was previously added. | |
190 */ | |
191 public static void unregisterObserver(Context context, AndroidSyncSettingsOb
server observer) { | |
192 ensureInitialized(context); | |
193 synchronized (sInstance.mLock) { | |
194 sInstance.mObservers.removeObserver(observer); | |
195 } | |
196 } | |
197 | |
198 private void setChromeSyncEnabled(boolean value) { | |
199 synchronized (mLock) { | |
200 updateSyncability(); | |
201 if (value == mChromeSyncEnabled || mAccount == null) return; | |
202 mChromeSyncEnabled = value; | |
203 | |
204 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites
(); | |
205 mSyncContentResolverDelegate.setSyncAutomatically(mAccount, mContrac
tAuthority, value); | |
206 StrictMode.setThreadPolicy(oldPolicy); | |
207 } | |
208 notifyObservers(); | |
209 } | |
210 | |
211 /** | |
212 * Ensure Chrome is registered with the Android Sync Manager iff signed in. | |
213 * | |
214 * This is what causes the "Chrome" option to appear in Settings -> Accounts
-> Sync . | |
215 * This function must be called within a synchronized block. | |
216 */ | |
217 private void updateSyncability() { | |
218 boolean shouldBeSyncable = mAccount != null; | |
219 if (mIsSyncable == shouldBeSyncable) return; | |
220 | |
221 mIsSyncable = shouldBeSyncable; | |
222 | |
223 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); | |
224 // Make account syncable if there is one. | |
225 if (shouldBeSyncable) { | |
226 mSyncContentResolverDelegate.setIsSyncable(mAccount, mContractAuthor
ity, 1); | |
227 // This reduces unnecessary resource usage. See http://crbug.com/480
688 for details. | |
228 mSyncContentResolverDelegate.removePeriodicSync( | |
229 mAccount, mContractAuthority, Bundle.EMPTY); | |
230 } | |
231 StrictMode.setThreadPolicy(oldPolicy); | |
232 | |
233 // Disable the syncability of Chrome for all other accounts. | |
234 AccountManagerHelper.get(mApplicationContext).getGoogleAccounts(new Call
back<Account[]>() { | |
235 @Override | |
236 public void onResult(Account[] accounts) { | |
237 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWr
ites(); | |
238 for (Account account : accounts) { | |
239 if (!account.equals(mAccount) && mSyncContentResolverDelegat
e.getIsSyncable( | |
240 account, mContractAuthority) > 0) { | |
241 mSyncContentResolverDelegate.setIsSyncable(account, mCon
tractAuthority, 0); | |
242 } | |
243 } | |
244 StrictMode.setThreadPolicy(oldPolicy); | |
245 } | |
246 }); | |
247 } | |
248 | |
249 /** | |
250 * Helper class to be used by observers whenever sync settings change. | |
251 * | |
252 * To register the observer, call AndroidSyncSettings.registerObserver(...). | |
253 */ | |
254 private class AndroidSyncSettingsChangedObserver implements SyncStatusObserv
er { | |
255 @Override | |
256 public void onStatusChanged(int which) { | |
257 if (which == ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) { | |
258 // Sync settings have changed; update our cached values. | |
259 if (updateCachedSettings()) { | |
260 // If something actually changed, tell our observers. | |
261 notifyObservers(); | |
262 } | |
263 } | |
264 } | |
265 } | |
266 | |
267 /** | |
268 * Update the three cached settings from the content resolver. | |
269 * | |
270 * @return Whether either chromeSyncEnabled or masterSyncEnabled changed. | |
271 */ | |
272 private boolean updateCachedSettings() { | |
273 synchronized (mLock) { | |
274 boolean oldChromeSyncEnabled = mChromeSyncEnabled; | |
275 boolean oldMasterSyncEnabled = mMasterSyncEnabled; | |
276 | |
277 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites
(); | |
278 if (mAccount != null) { | |
279 mIsSyncable = mSyncContentResolverDelegate.getIsSyncable( | |
280 mAccount, mContractAuthority) == 1; | |
281 mChromeSyncEnabled = mSyncContentResolverDelegate.getSyncAutomat
ically( | |
282 mAccount, mContractAuthority); | |
283 } else { | |
284 mIsSyncable = false; | |
285 mChromeSyncEnabled = false; | |
286 } | |
287 mMasterSyncEnabled = mSyncContentResolverDelegate.getMasterSyncAutom
atically(); | |
288 StrictMode.setThreadPolicy(oldPolicy); | |
289 | |
290 return oldChromeSyncEnabled != mChromeSyncEnabled | |
291 || oldMasterSyncEnabled != mMasterSyncEnabled; | |
292 } | |
293 } | |
294 | |
295 private void notifyObservers() { | |
296 for (AndroidSyncSettingsObserver observer : mObservers) { | |
297 observer.androidSyncSettingsChanged(); | |
298 } | |
299 } | |
300 | |
301 private String getContractAuthority() { | |
302 return mApplicationContext.getPackageName(); | |
303 } | |
304 } | |
OLD | NEW |