| 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 |