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.
5 package org.chromium.chrome.browser.sync;
7 import android.content.Context;
8 import android.util.Log;
10 import com.google.common.annotations.VisibleForTesting;
12 import org.chromium.base.CalledByNative;
13 import org.chromium.base.ThreadUtils;
14 import org.chromium.chrome.browser.identity.UniqueIdentificationGenerator;
15 import org.chromium.sync.internal_api.pub.SyncDecryptionPassphraseType;
16 import org.chromium.sync.internal_api.pub.base.ModelType;
18 import java.util.HashSet;
19 import java.util.List;
21 import java.util.concurrent.CopyOnWriteArrayList;
24 * Android wrapper of the ProfileSyncService which provides access from the Java layer.
26 * This class mostly wraps native classes, but it make a few business logic decisions, both in Java
29 * Only usable from the UI thread as the native ProfileSyncService requires its access to be in the
32 * See chrome/browser/sync/profile_sync_service.h for more details.
34 public class ProfileSyncService {
36 public interface SyncStateChangedListener {
37 // Invoked when the underlying sync status has changed.
38 public void syncStateChanged();
41 private static final String TAG = "ProfileSyncService";
44 public static final String SESSION_TAG_PREFIX = "session_sync";
46 private static ProfileSyncService sSyncSetupManager;
49 protected final Context mContext;
51 // Sync state changes more often than listeners are added/removed, so using CopyOnWrite.
52 private final List<SyncStateChangedListener> mListeners =
53 new CopyOnWriteArrayList<SyncStateChangedListener>();
55 // Native ProfileSyncServiceAndroid object. Can not be final since we set it to 0 in destroy().
56 private final int mNativeProfileSyncServiceAndroid;
59 * A helper method for retrieving the application-wide SyncSetupManager.
61 * Can only be accessed on the main thread.
63 * @param context the ApplicationContext is retrieved from the context used as an argument.
64 * @return a singleton instance of the SyncSetupManager
66 public static ProfileSyncService get(Context context) {
67 ThreadUtils.assertOnUiThread();
68 if (sSyncSetupManager == null) {
69 sSyncSetupManager = new ProfileSyncService(context);
71 return sSyncSetupManager;
75 * This is called pretty early in our application. Avoid any blocking operations here.
77 private ProfileSyncService(Context context) {
78 ThreadUtils.assertOnUiThread();
79 // We should store the application context, as we outlive any activity which may create us.
80 mContext = context.getApplicationContext();
82 // This may cause us to create ProfileSyncService even if sync has not
83 // been set up, but ProfileSyncService::Startup() won't be called until
84 // credentials are available.
85 mNativeProfileSyncServiceAndroid = nativeInit();
89 private static int getProfileSyncServiceAndroid(Context context) {
90 return get(context).mNativeProfileSyncServiceAndroid;
94 * If we are currently in the process of setting up sync, this method clears the
95 * sync setup in progress flag.
98 public void finishSyncFirstSetupIfNeeded() {
99 if (isFirstSetupInProgress()) {
100 setSyncSetupCompleted();
101 setSetupInProgress(false);
105 public void signOut() {
106 nativeSignOutSync(mNativeProfileSyncServiceAndroid);
110 * Signs in to sync, using the currently signed-in account.
112 public void syncSignIn() {
113 nativeSignInSync(mNativeProfileSyncServiceAndroid);
114 // Notify listeners right away that the sync state has changed (native side does not do
120 * Signs in to sync, using the existing auth token.
123 public void syncSignIn(String account) {
130 * @param account The username of the account that is signing in.
131 * @param authToken Not used. ProfileSyncService switched to OAuth2 tokens.
132 * Deprecated. Use syncSignIn instead.
135 public void syncSignInWithAuthToken(String account, String authToken) {
139 public void requestSyncFromNativeChrome(
140 int objectSource, String objectId, long version, String payload) {
141 ThreadUtils.assertOnUiThread();
143 mNativeProfileSyncServiceAndroid, objectSource, objectId, version, payload);
146 public void requestSyncFromNativeChromeForAllTypes() {
147 ThreadUtils.assertOnUiThread();
148 nativeNudgeSyncerForAllTypes(mNativeProfileSyncServiceAndroid);
152 * Nudge the syncer to start a new sync cycle.
155 public void requestSyncCycleForTest() {
156 ThreadUtils.assertOnUiThread();
157 requestSyncFromNativeChromeForAllTypes();
160 public String querySyncStatus() {
161 ThreadUtils.assertOnUiThread();
162 return nativeQuerySyncStatusSummary(mNativeProfileSyncServiceAndroid);
166 * Sets the the machine tag used by session sync to a unique value.
168 public void setSessionsId(UniqueIdentificationGenerator generator) {
169 ThreadUtils.assertOnUiThread();
170 String uniqueTag = generator.getUniqueId(null);
171 if (uniqueTag.isEmpty()) {
172 Log.e(TAG, "Unable to get unique tag for sync. " +
173 "This may lead to unexpected tab sync behavior.");
176 String sessionTag = SESSION_TAG_PREFIX + uniqueTag;
177 if (!nativeSetSyncSessionsId(mNativeProfileSyncServiceAndroid, sessionTag)) {
178 Log.e(TAG, "Unable to write session sync tag. " +
179 "This may lead to unexpected tab sync behavior.");
184 * Checks if a password or a passphrase is required for decryption of sync data.
186 * Returns NONE if the state is unavailable, or decryption passphrase/password is not required.
188 * @return the enum describing the decryption passphrase type required
190 public SyncDecryptionPassphraseType getSyncDecryptionPassphraseTypeIfRequired() {
191 // ProfileSyncService::IsUsingSecondaryPassphrase() requires the sync backend to be
192 // initialized, and that happens just after OnPassphraseRequired(). Therefore, we need to
193 // guard that call with a check of the sync backend since we can not be sure which
194 // passphrase type we should tell the user we need.
195 // This is tracked in:
196 // http://code.google.com/p/chromium/issues/detail?id=108127
197 if (isSyncInitialized() && isPassphraseRequiredForDecryption()) {
198 return getSyncDecryptionPassphraseType();
200 return SyncDecryptionPassphraseType.NONE;
204 * Returns the actual passphrase type being used for encryption. The sync backend must be
205 * running (isSyncInitialized() returns true) before calling this function.
207 * This method should only be used if you want to know the raw value. For checking whether we
208 * should ask the user for a passphrase, you should instead use
209 * getSyncDecryptionPassphraseTypeIfRequired().
211 public SyncDecryptionPassphraseType getSyncDecryptionPassphraseType() {
212 assert isSyncInitialized();
213 int passphraseType = nativeGetPassphraseType(mNativeProfileSyncServiceAndroid);
214 return SyncDecryptionPassphraseType.fromInternalValue(passphraseType);
217 public boolean isSyncKeystoreMigrationDone() {
218 assert isSyncInitialized();
219 return nativeIsSyncKeystoreMigrationDone(mNativeProfileSyncServiceAndroid);
223 * Returns true if the current explicit passphrase time is defined.
225 public boolean hasExplicitPassphraseTime() {
226 assert isSyncInitialized();
227 return nativeHasExplicitPassphraseTime(mNativeProfileSyncServiceAndroid);
230 public String getSyncEnterGooglePassphraseBodyWithDateText() {
231 assert isSyncInitialized();
232 return nativeGetSyncEnterGooglePassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
235 public String getSyncEnterCustomPassphraseBodyWithDateText() {
236 assert isSyncInitialized();
237 return nativeGetSyncEnterCustomPassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
240 public String getCurrentSignedInAccountText() {
241 assert isSyncInitialized();
242 return nativeGetCurrentSignedInAccountText(mNativeProfileSyncServiceAndroid);
245 public String getSyncEnterCustomPassphraseBodyText() {
246 return nativeGetSyncEnterCustomPassphraseBodyText(mNativeProfileSyncServiceAndroid);
250 * Checks if sync is currently set to use a custom passphrase. The sync backend must be running
251 * (isSyncInitialized() returns true) before calling this function.
253 * @return true if sync is using a custom passphrase.
255 public boolean isUsingSecondaryPassphrase() {
256 assert isSyncInitialized();
257 return nativeIsUsingSecondaryPassphrase(mNativeProfileSyncServiceAndroid);
261 * Checks if we need a passphrase to decrypt a currently-enabled data type. This returns false
262 * if a passphrase is needed for a type that is not currently enabled.
264 * @return true if we need a passphrase.
266 public boolean isPassphraseRequiredForDecryption() {
267 assert isSyncInitialized();
268 return nativeIsPassphraseRequiredForDecryption(mNativeProfileSyncServiceAndroid);
272 * Checks if we need a passphrase to decrypt any data type (including types that aren't
273 * currently enabled or supported, such as passwords). This API is used to determine if we
274 * need to provide a decryption passphrase before we can re-encrypt with a custom passphrase.
276 * @return true if we need a passphrase for some type.
278 public boolean isPassphraseRequiredForExternalType() {
279 assert isSyncInitialized();
280 return nativeIsPassphraseRequiredForExternalType(mNativeProfileSyncServiceAndroid);
284 * Checks if the sync backend is running.
286 * @return true if sync is initialized/running.
288 public boolean isSyncInitialized() {
289 return nativeIsSyncInitialized(mNativeProfileSyncServiceAndroid);
293 * Checks if the first sync setup is currently in progress.
295 * @return true if first sync setup is in progress
297 public boolean isFirstSetupInProgress() {
298 return nativeIsFirstSetupInProgress(mNativeProfileSyncServiceAndroid);
302 * Checks if the all the data types are encrypted.
304 * @return true if all data types are encrypted, false if only passwords are encrypted.
306 public boolean isEncryptEverythingEnabled() {
307 assert isSyncInitialized();
308 return nativeIsEncryptEverythingEnabled(mNativeProfileSyncServiceAndroid);
312 * Turns on encryption of all data types. This only takes effect after sync configuration is
313 * completed and setPreferredDataTypes() is invoked.
315 public void enableEncryptEverything() {
316 assert isSyncInitialized();
317 nativeEnableEncryptEverything(mNativeProfileSyncServiceAndroid);
320 public void setEncryptionPassphrase(String passphrase, boolean isGaia) {
321 assert isSyncInitialized();
322 nativeSetEncryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase, isGaia);
325 public boolean isCryptographerReady() {
326 assert isSyncInitialized();
327 return nativeIsCryptographerReady(mNativeProfileSyncServiceAndroid);
330 public boolean setDecryptionPassphrase(String passphrase) {
331 assert isSyncInitialized();
332 return nativeSetDecryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase);
335 public GoogleServiceAuthError.State getAuthError() {
336 int authErrorCode = nativeGetAuthError(mNativeProfileSyncServiceAndroid);
337 return GoogleServiceAuthError.State.fromCode(authErrorCode);
341 * Gets the set of data types that are currently enabled to sync.
343 * @return Set of enabled types.
345 public Set<ModelType> getPreferredDataTypes() {
346 long modelTypeSelection =
347 nativeGetEnabledDataTypes(mNativeProfileSyncServiceAndroid);
348 Set<ModelType> syncTypes = new HashSet<ModelType>();
349 if ((modelTypeSelection & ModelTypeSelection.AUTOFILL) != 0) {
350 syncTypes.add(ModelType.AUTOFILL);
352 if ((modelTypeSelection & ModelTypeSelection.AUTOFILL_PROFILE) != 0) {
353 syncTypes.add(ModelType.AUTOFILL_PROFILE);
355 if ((modelTypeSelection & ModelTypeSelection.BOOKMARK) != 0) {
356 syncTypes.add(ModelType.BOOKMARK);
358 if ((modelTypeSelection & ModelTypeSelection.EXPERIMENTS) != 0) {
359 syncTypes.add(ModelType.EXPERIMENTS);
361 if ((modelTypeSelection & ModelTypeSelection.NIGORI) != 0) {
362 syncTypes.add(ModelType.NIGORI);
364 if ((modelTypeSelection & ModelTypeSelection.PASSWORD) != 0) {
365 syncTypes.add(ModelType.PASSWORD);
367 if ((modelTypeSelection & ModelTypeSelection.SESSION) != 0) {
368 syncTypes.add(ModelType.SESSION);
370 if ((modelTypeSelection & ModelTypeSelection.TYPED_URL) != 0) {
371 syncTypes.add(ModelType.TYPED_URL);
373 if ((modelTypeSelection & ModelTypeSelection.HISTORY_DELETE_DIRECTIVE) != 0) {
374 syncTypes.add(ModelType.HISTORY_DELETE_DIRECTIVE);
376 if ((modelTypeSelection & ModelTypeSelection.DEVICE_INFO) != 0) {
377 syncTypes.add(ModelType.DEVICE_INFO);
379 if ((modelTypeSelection & ModelTypeSelection.PROXY_TABS) != 0) {
380 syncTypes.add(ModelType.PROXY_TABS);
382 if ((modelTypeSelection & ModelTypeSelection.FAVICON_IMAGE) != 0) {
383 syncTypes.add(ModelType.FAVICON_IMAGE);
385 if ((modelTypeSelection & ModelTypeSelection.FAVICON_TRACKING) != 0) {
386 syncTypes.add(ModelType.FAVICON_TRACKING);
391 public boolean hasKeepEverythingSynced() {
392 return nativeHasKeepEverythingSynced(mNativeProfileSyncServiceAndroid);
396 * Enables syncing for the passed data types.
398 * @param syncEverything Set to true if the user wants to sync all data types
399 * (including new data types we add in the future).
400 * @param enabledTypes The set of types to enable. Ignored (can be null) if
401 * syncEverything is true.
403 public void setPreferredDataTypes(boolean syncEverything, Set<ModelType> enabledTypes) {
404 long modelTypeSelection = 0;
405 if (syncEverything || enabledTypes.contains(ModelType.AUTOFILL)) {
406 modelTypeSelection |= ModelTypeSelection.AUTOFILL;
408 if (syncEverything || enabledTypes.contains(ModelType.BOOKMARK)) {
409 modelTypeSelection |= ModelTypeSelection.BOOKMARK;
411 if (syncEverything || enabledTypes.contains(ModelType.PASSWORD)) {
412 modelTypeSelection |= ModelTypeSelection.PASSWORD;
414 if (syncEverything || enabledTypes.contains(ModelType.PROXY_TABS)) {
415 modelTypeSelection |= ModelTypeSelection.PROXY_TABS;
417 if (syncEverything || enabledTypes.contains(ModelType.TYPED_URL)) {
418 modelTypeSelection |= ModelTypeSelection.TYPED_URL;
420 nativeSetPreferredDataTypes(
421 mNativeProfileSyncServiceAndroid, syncEverything, modelTypeSelection);
424 public void setSyncSetupCompleted() {
425 nativeSetSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
428 public boolean hasSyncSetupCompleted() {
429 return nativeHasSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
432 public boolean isStartSuppressed() {
433 return nativeIsStartSuppressed(mNativeProfileSyncServiceAndroid);
437 * Notifies sync whether sync setup is in progress - this tells sync whether it should start
438 * syncing data types when it starts up, or if it should just stay in "configuration mode".
440 * @param inProgress True to put sync in configuration mode, false to turn off configuration
443 public void setSetupInProgress(boolean inProgress) {
444 nativeSetSetupInProgress(mNativeProfileSyncServiceAndroid, inProgress);
447 public void addSyncStateChangedListener(SyncStateChangedListener listener) {
448 ThreadUtils.assertOnUiThread();
449 mListeners.add(listener);
452 public void removeSyncStateChangedListener(SyncStateChangedListener listener) {
453 ThreadUtils.assertOnUiThread();
454 mListeners.remove(listener);
457 public boolean hasUnrecoverableError() {
458 return nativeHasUnrecoverableError(mNativeProfileSyncServiceAndroid);
462 * Called when the state of the native sync engine has changed, so various
463 * UI elements can update themselves.
466 public void syncStateChanged() {
467 if (!mListeners.isEmpty()) {
468 for (SyncStateChangedListener listener : mListeners) {
469 listener.syncStateChanged();
475 public String getSyncInternalsInfoForTest() {
476 ThreadUtils.assertOnUiThread();
477 return nativeGetAboutInfoForTest(mNativeProfileSyncServiceAndroid);
481 * Starts the sync engine.
483 public void enableSync() {
484 nativeEnableSync(mNativeProfileSyncServiceAndroid);
488 * Stops the sync engine.
490 public void disableSync() {
491 nativeDisableSync(mNativeProfileSyncServiceAndroid);
495 * Returns the time when the last sync cycle was completed.
497 * @return The difference measured in microseconds, between last sync cycle completion time
498 * and 1 January 1970 00:00:00 UTC.
500 public long getLastSyncedTimeForTest() {
501 return nativeGetLastSyncedTimeForTest(mNativeProfileSyncServiceAndroid);
505 private native void nativeNudgeSyncer(
506 int nativeProfileSyncServiceAndroid, int objectSource, String objectId, long version,
508 private native void nativeNudgeSyncerForAllTypes(int nativeProfileSyncServiceAndroid);
509 private native int nativeInit();
510 private native void nativeEnableSync(int nativeProfileSyncServiceAndroid);
511 private native void nativeDisableSync(int nativeProfileSyncServiceAndroid);
512 private native void nativeSignInSync(int nativeProfileSyncServiceAndroid);
513 private native void nativeSignOutSync(int nativeProfileSyncServiceAndroid);
514 private native boolean nativeSetSyncSessionsId(int nativeProfileSyncServiceAndroid, String tag);
515 private native String nativeQuerySyncStatusSummary(int nativeProfileSyncServiceAndroid);
516 private native int nativeGetAuthError(int nativeProfileSyncServiceAndroid);
517 private native boolean nativeIsSyncInitialized(int nativeProfileSyncServiceAndroid);
518 private native boolean nativeIsFirstSetupInProgress(int nativeProfileSyncServiceAndroid);
519 private native boolean nativeIsEncryptEverythingEnabled(int nativeProfileSyncServiceAndroid);
520 private native void nativeEnableEncryptEverything(int nativeProfileSyncServiceAndroid);
521 private native boolean nativeIsPassphraseRequiredForDecryption(
522 int nativeProfileSyncServiceAndroid);
523 private native boolean nativeIsPassphraseRequiredForExternalType(
524 int nativeProfileSyncServiceAndroid);
525 private native boolean nativeIsUsingSecondaryPassphrase(int nativeProfileSyncServiceAndroid);
526 private native boolean nativeSetDecryptionPassphrase(
527 int nativeProfileSyncServiceAndroid, String passphrase);
528 private native void nativeSetEncryptionPassphrase(
529 int nativeProfileSyncServiceAndroid, String passphrase, boolean isGaia);
530 private native boolean nativeIsCryptographerReady(int nativeProfileSyncServiceAndroid);
531 private native int nativeGetPassphraseType(int nativeProfileSyncServiceAndroid);
532 private native boolean nativeHasExplicitPassphraseTime(int nativeProfileSyncServiceAndroid);
533 private native String nativeGetSyncEnterGooglePassphraseBodyWithDateText(
534 int nativeProfileSyncServiceAndroid);
535 private native String nativeGetSyncEnterCustomPassphraseBodyWithDateText(
536 int nativeProfileSyncServiceAndroid);
537 private native String nativeGetCurrentSignedInAccountText(int nativeProfileSyncServiceAndroid);
538 private native String nativeGetSyncEnterCustomPassphraseBodyText(
539 int nativeProfileSyncServiceAndroid);
540 private native boolean nativeIsSyncKeystoreMigrationDone(int nativeProfileSyncServiceAndroid);
541 private native long nativeGetEnabledDataTypes(
542 int nativeProfileSyncServiceAndroid);
543 private native void nativeSetPreferredDataTypes(
544 int nativeProfileSyncServiceAndroid, boolean syncEverything, long modelTypeSelection);
545 private native void nativeSetSetupInProgress(
546 int nativeProfileSyncServiceAndroid, boolean inProgress);
547 private native void nativeSetSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
548 private native boolean nativeHasSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
549 private native boolean nativeIsStartSuppressed(int nativeProfileSyncServiceAndroid);
550 private native boolean nativeHasKeepEverythingSynced(int nativeProfileSyncServiceAndroid);
551 private native boolean nativeHasUnrecoverableError(int nativeProfileSyncServiceAndroid);
552 private native String nativeGetAboutInfoForTest(int nativeProfileSyncServiceAndroid);
553 private native long nativeGetLastSyncedTimeForTest(int nativeProfileSyncServiceAndroid);