blob: 4ebfac997e26edd4718aa87ab375fed1a37d5bea [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.sync;
import android.content.Context;
import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import org.chromium.base.CalledByNative;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.identity.UniqueIdentificationGenerator;
import org.chromium.sync.internal_api.pub.SyncDecryptionPassphraseType;
import org.chromium.sync.internal_api.pub.base.ModelType;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Android wrapper of the ProfileSyncService which provides access from the Java layer.
* <p/>
* This class mostly wraps native classes, but it make a few business logic decisions, both in Java
* and in native.
* <p/>
* Only usable from the UI thread as the native ProfileSyncService requires its access to be in the
* UI thread.
* <p/>
* See chrome/browser/sync/profile_sync_service.h for more details.
*/
public class ProfileSyncService {
public interface SyncStateChangedListener {
// Invoked when the underlying sync status has changed.
public void syncStateChanged();
}
private static final String TAG = "ProfileSyncService";
@VisibleForTesting
public static final String SESSION_TAG_PREFIX = "session_sync";
private static ProfileSyncService sSyncSetupManager;
@VisibleForTesting
protected final Context mContext;
// Sync state changes more often than listeners are added/removed, so using CopyOnWrite.
private final List<SyncStateChangedListener> mListeners =
new CopyOnWriteArrayList<SyncStateChangedListener>();
// Native ProfileSyncServiceAndroid object. Can not be final since we set it to 0 in destroy().
private final int mNativeProfileSyncServiceAndroid;
/**
* A helper method for retrieving the application-wide SyncSetupManager.
* <p/>
* Can only be accessed on the main thread.
*
* @param context the ApplicationContext is retrieved from the context used as an argument.
* @return a singleton instance of the SyncSetupManager
*/
public static ProfileSyncService get(Context context) {
ThreadUtils.assertOnUiThread();
if (sSyncSetupManager == null) {
sSyncSetupManager = new ProfileSyncService(context);
}
return sSyncSetupManager;
}
/**
* This is called pretty early in our application. Avoid any blocking operations here.
*/
private ProfileSyncService(Context context) {
ThreadUtils.assertOnUiThread();
// We should store the application context, as we outlive any activity which may create us.
mContext = context.getApplicationContext();
// This may cause us to create ProfileSyncService even if sync has not
// been set up, but ProfileSyncService::Startup() won't be called until
// credentials are available.
mNativeProfileSyncServiceAndroid = nativeInit();
}
@CalledByNative
private static int getProfileSyncServiceAndroid(Context context) {
return get(context).mNativeProfileSyncServiceAndroid;
}
/**
* If we are currently in the process of setting up sync, this method clears the
* sync setup in progress flag.
*/
@VisibleForTesting
public void finishSyncFirstSetupIfNeeded() {
if (isFirstSetupInProgress()) {
setSyncSetupCompleted();
setSetupInProgress(false);
}
}
public void signOut() {
nativeSignOutSync(mNativeProfileSyncServiceAndroid);
}
/**
* Signs in to sync, using the currently signed-in account.
*/
public void syncSignIn() {
nativeSignInSync(mNativeProfileSyncServiceAndroid);
// Notify listeners right away that the sync state has changed (native side does not do
// this)
syncStateChanged();
}
/**
* Signs in to sync, using the existing auth token.
*/
@Deprecated
public void syncSignIn(String account) {
syncSignIn();
}
/**
* Signs in to sync.
*
* @param account The username of the account that is signing in.
* @param authToken Not used. ProfileSyncService switched to OAuth2 tokens.
* Deprecated. Use syncSignIn instead.
*/
@Deprecated
public void syncSignInWithAuthToken(String account, String authToken) {
syncSignIn(account);
}
public void requestSyncFromNativeChrome(
int objectSource, String objectId, long version, String payload) {
ThreadUtils.assertOnUiThread();
nativeNudgeSyncer(
mNativeProfileSyncServiceAndroid, objectSource, objectId, version, payload);
}
public void requestSyncFromNativeChromeForAllTypes() {
ThreadUtils.assertOnUiThread();
nativeNudgeSyncerForAllTypes(mNativeProfileSyncServiceAndroid);
}
/**
* Nudge the syncer to start a new sync cycle.
*/
@VisibleForTesting
public void requestSyncCycleForTest() {
ThreadUtils.assertOnUiThread();
requestSyncFromNativeChromeForAllTypes();
}
public String querySyncStatus() {
ThreadUtils.assertOnUiThread();
return nativeQuerySyncStatusSummary(mNativeProfileSyncServiceAndroid);
}
/**
* Sets the the machine tag used by session sync to a unique value.
*/
public void setSessionsId(UniqueIdentificationGenerator generator) {
ThreadUtils.assertOnUiThread();
String uniqueTag = generator.getUniqueId(null);
if (uniqueTag.isEmpty()) {
Log.e(TAG, "Unable to get unique tag for sync. " +
"This may lead to unexpected tab sync behavior.");
return;
}
String sessionTag = SESSION_TAG_PREFIX + uniqueTag;
if (!nativeSetSyncSessionsId(mNativeProfileSyncServiceAndroid, sessionTag)) {
Log.e(TAG, "Unable to write session sync tag. " +
"This may lead to unexpected tab sync behavior.");
}
}
/**
* Checks if a password or a passphrase is required for decryption of sync data.
* <p/>
* Returns NONE if the state is unavailable, or decryption passphrase/password is not required.
*
* @return the enum describing the decryption passphrase type required
*/
public SyncDecryptionPassphraseType getSyncDecryptionPassphraseTypeIfRequired() {
// ProfileSyncService::IsUsingSecondaryPassphrase() requires the sync backend to be
// initialized, and that happens just after OnPassphraseRequired(). Therefore, we need to
// guard that call with a check of the sync backend since we can not be sure which
// passphrase type we should tell the user we need.
// This is tracked in:
// http://code.google.com/p/chromium/issues/detail?id=108127
if (isSyncInitialized() && isPassphraseRequiredForDecryption()) {
return getSyncDecryptionPassphraseType();
}
return SyncDecryptionPassphraseType.NONE;
}
/**
* Returns the actual passphrase type being used for encryption. The sync backend must be
* running (isSyncInitialized() returns true) before calling this function.
* <p/>
* This method should only be used if you want to know the raw value. For checking whether we
* should ask the user for a passphrase, you should instead use
* getSyncDecryptionPassphraseTypeIfRequired().
*/
public SyncDecryptionPassphraseType getSyncDecryptionPassphraseType() {
assert isSyncInitialized();
int passphraseType = nativeGetPassphraseType(mNativeProfileSyncServiceAndroid);
return SyncDecryptionPassphraseType.fromInternalValue(passphraseType);
}
public boolean isSyncKeystoreMigrationDone() {
assert isSyncInitialized();
return nativeIsSyncKeystoreMigrationDone(mNativeProfileSyncServiceAndroid);
}
/**
* Returns true if the current explicit passphrase time is defined.
*/
public boolean hasExplicitPassphraseTime() {
assert isSyncInitialized();
return nativeHasExplicitPassphraseTime(mNativeProfileSyncServiceAndroid);
}
public String getSyncEnterGooglePassphraseBodyWithDateText() {
assert isSyncInitialized();
return nativeGetSyncEnterGooglePassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
}
public String getSyncEnterCustomPassphraseBodyWithDateText() {
assert isSyncInitialized();
return nativeGetSyncEnterCustomPassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
}
public String getCurrentSignedInAccountText() {
assert isSyncInitialized();
return nativeGetCurrentSignedInAccountText(mNativeProfileSyncServiceAndroid);
}
public String getSyncEnterCustomPassphraseBodyText() {
return nativeGetSyncEnterCustomPassphraseBodyText(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if sync is currently set to use a custom passphrase. The sync backend must be running
* (isSyncInitialized() returns true) before calling this function.
*
* @return true if sync is using a custom passphrase.
*/
public boolean isUsingSecondaryPassphrase() {
assert isSyncInitialized();
return nativeIsUsingSecondaryPassphrase(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if we need a passphrase to decrypt a currently-enabled data type. This returns false
* if a passphrase is needed for a type that is not currently enabled.
*
* @return true if we need a passphrase.
*/
public boolean isPassphraseRequiredForDecryption() {
assert isSyncInitialized();
return nativeIsPassphraseRequiredForDecryption(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if we need a passphrase to decrypt any data type (including types that aren't
* currently enabled or supported, such as passwords). This API is used to determine if we
* need to provide a decryption passphrase before we can re-encrypt with a custom passphrase.
*
* @return true if we need a passphrase for some type.
*/
public boolean isPassphraseRequiredForExternalType() {
assert isSyncInitialized();
return nativeIsPassphraseRequiredForExternalType(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if the sync backend is running.
*
* @return true if sync is initialized/running.
*/
public boolean isSyncInitialized() {
return nativeIsSyncInitialized(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if the first sync setup is currently in progress.
*
* @return true if first sync setup is in progress
*/
public boolean isFirstSetupInProgress() {
return nativeIsFirstSetupInProgress(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if the all the data types are encrypted.
*
* @return true if all data types are encrypted, false if only passwords are encrypted.
*/
public boolean isEncryptEverythingEnabled() {
assert isSyncInitialized();
return nativeIsEncryptEverythingEnabled(mNativeProfileSyncServiceAndroid);
}
/**
* Turns on encryption of all data types. This only takes effect after sync configuration is
* completed and setPreferredDataTypes() is invoked.
*/
public void enableEncryptEverything() {
assert isSyncInitialized();
nativeEnableEncryptEverything(mNativeProfileSyncServiceAndroid);
}
public void setEncryptionPassphrase(String passphrase, boolean isGaia) {
assert isSyncInitialized();
nativeSetEncryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase, isGaia);
}
public boolean isCryptographerReady() {
assert isSyncInitialized();
return nativeIsCryptographerReady(mNativeProfileSyncServiceAndroid);
}
public boolean setDecryptionPassphrase(String passphrase) {
assert isSyncInitialized();
return nativeSetDecryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase);
}
public GoogleServiceAuthError.State getAuthError() {
int authErrorCode = nativeGetAuthError(mNativeProfileSyncServiceAndroid);
return GoogleServiceAuthError.State.fromCode(authErrorCode);
}
/**
* Gets the set of data types that are currently enabled to sync.
*
* @return Set of enabled types.
*/
public Set<ModelType> getPreferredDataTypes() {
long modelTypeSelection =
nativeGetEnabledDataTypes(mNativeProfileSyncServiceAndroid);
Set<ModelType> syncTypes = new HashSet<ModelType>();
if ((modelTypeSelection & ModelTypeSelection.AUTOFILL) != 0) {
syncTypes.add(ModelType.AUTOFILL);
}
if ((modelTypeSelection & ModelTypeSelection.AUTOFILL_PROFILE) != 0) {
syncTypes.add(ModelType.AUTOFILL_PROFILE);
}
if ((modelTypeSelection & ModelTypeSelection.BOOKMARK) != 0) {
syncTypes.add(ModelType.BOOKMARK);
}
if ((modelTypeSelection & ModelTypeSelection.EXPERIMENTS) != 0) {
syncTypes.add(ModelType.EXPERIMENTS);
}
if ((modelTypeSelection & ModelTypeSelection.NIGORI) != 0) {
syncTypes.add(ModelType.NIGORI);
}
if ((modelTypeSelection & ModelTypeSelection.PASSWORD) != 0) {
syncTypes.add(ModelType.PASSWORD);
}
if ((modelTypeSelection & ModelTypeSelection.SESSION) != 0) {
syncTypes.add(ModelType.SESSION);
}
if ((modelTypeSelection & ModelTypeSelection.TYPED_URL) != 0) {
syncTypes.add(ModelType.TYPED_URL);
}
if ((modelTypeSelection & ModelTypeSelection.HISTORY_DELETE_DIRECTIVE) != 0) {
syncTypes.add(ModelType.HISTORY_DELETE_DIRECTIVE);
}
if ((modelTypeSelection & ModelTypeSelection.DEVICE_INFO) != 0) {
syncTypes.add(ModelType.DEVICE_INFO);
}
if ((modelTypeSelection & ModelTypeSelection.PROXY_TABS) != 0) {
syncTypes.add(ModelType.PROXY_TABS);
}
if ((modelTypeSelection & ModelTypeSelection.FAVICON_IMAGE) != 0) {
syncTypes.add(ModelType.FAVICON_IMAGE);
}
if ((modelTypeSelection & ModelTypeSelection.FAVICON_TRACKING) != 0) {
syncTypes.add(ModelType.FAVICON_TRACKING);
}
return syncTypes;
}
public boolean hasKeepEverythingSynced() {
return nativeHasKeepEverythingSynced(mNativeProfileSyncServiceAndroid);
}
/**
* Enables syncing for the passed data types.
*
* @param syncEverything Set to true if the user wants to sync all data types
* (including new data types we add in the future).
* @param enabledTypes The set of types to enable. Ignored (can be null) if
* syncEverything is true.
*/
public void setPreferredDataTypes(boolean syncEverything, Set<ModelType> enabledTypes) {
long modelTypeSelection = 0;
if (syncEverything || enabledTypes.contains(ModelType.AUTOFILL)) {
modelTypeSelection |= ModelTypeSelection.AUTOFILL;
}
if (syncEverything || enabledTypes.contains(ModelType.BOOKMARK)) {
modelTypeSelection |= ModelTypeSelection.BOOKMARK;
}
if (syncEverything || enabledTypes.contains(ModelType.PASSWORD)) {
modelTypeSelection |= ModelTypeSelection.PASSWORD;
}
if (syncEverything || enabledTypes.contains(ModelType.PROXY_TABS)) {
modelTypeSelection |= ModelTypeSelection.PROXY_TABS;
}
if (syncEverything || enabledTypes.contains(ModelType.TYPED_URL)) {
modelTypeSelection |= ModelTypeSelection.TYPED_URL;
}
nativeSetPreferredDataTypes(
mNativeProfileSyncServiceAndroid, syncEverything, modelTypeSelection);
}
public void setSyncSetupCompleted() {
nativeSetSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
}
public boolean hasSyncSetupCompleted() {
return nativeHasSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
}
public boolean isStartSuppressed() {
return nativeIsStartSuppressed(mNativeProfileSyncServiceAndroid);
}
/**
* Notifies sync whether sync setup is in progress - this tells sync whether it should start
* syncing data types when it starts up, or if it should just stay in "configuration mode".
*
* @param inProgress True to put sync in configuration mode, false to turn off configuration
* and allow syncing.
*/
public void setSetupInProgress(boolean inProgress) {
nativeSetSetupInProgress(mNativeProfileSyncServiceAndroid, inProgress);
}
public void addSyncStateChangedListener(SyncStateChangedListener listener) {
ThreadUtils.assertOnUiThread();
mListeners.add(listener);
}
public void removeSyncStateChangedListener(SyncStateChangedListener listener) {
ThreadUtils.assertOnUiThread();
mListeners.remove(listener);
}
public boolean hasUnrecoverableError() {
return nativeHasUnrecoverableError(mNativeProfileSyncServiceAndroid);
}
/**
* Called when the state of the native sync engine has changed, so various
* UI elements can update themselves.
*/
@CalledByNative
public void syncStateChanged() {
if (!mListeners.isEmpty()) {
for (SyncStateChangedListener listener : mListeners) {
listener.syncStateChanged();
}
}
}
@VisibleForTesting
public String getSyncInternalsInfoForTest() {
ThreadUtils.assertOnUiThread();
return nativeGetAboutInfoForTest(mNativeProfileSyncServiceAndroid);
}
/**
* Starts the sync engine.
*/
public void enableSync() {
nativeEnableSync(mNativeProfileSyncServiceAndroid);
}
/**
* Stops the sync engine.
*/
public void disableSync() {
nativeDisableSync(mNativeProfileSyncServiceAndroid);
}
/**
* Returns the time when the last sync cycle was completed.
*
* @return The difference measured in microseconds, between last sync cycle completion time
* and 1 January 1970 00:00:00 UTC.
*/
public long getLastSyncedTimeForTest() {
return nativeGetLastSyncedTimeForTest(mNativeProfileSyncServiceAndroid);
}
// Native methods
private native void nativeNudgeSyncer(
int nativeProfileSyncServiceAndroid, int objectSource, String objectId, long version,
String payload);
private native void nativeNudgeSyncerForAllTypes(int nativeProfileSyncServiceAndroid);
private native int nativeInit();
private native void nativeEnableSync(int nativeProfileSyncServiceAndroid);
private native void nativeDisableSync(int nativeProfileSyncServiceAndroid);
private native void nativeSignInSync(int nativeProfileSyncServiceAndroid);
private native void nativeSignOutSync(int nativeProfileSyncServiceAndroid);
private native boolean nativeSetSyncSessionsId(int nativeProfileSyncServiceAndroid, String tag);
private native String nativeQuerySyncStatusSummary(int nativeProfileSyncServiceAndroid);
private native int nativeGetAuthError(int nativeProfileSyncServiceAndroid);
private native boolean nativeIsSyncInitialized(int nativeProfileSyncServiceAndroid);
private native boolean nativeIsFirstSetupInProgress(int nativeProfileSyncServiceAndroid);
private native boolean nativeIsEncryptEverythingEnabled(int nativeProfileSyncServiceAndroid);
private native void nativeEnableEncryptEverything(int nativeProfileSyncServiceAndroid);
private native boolean nativeIsPassphraseRequiredForDecryption(
int nativeProfileSyncServiceAndroid);
private native boolean nativeIsPassphraseRequiredForExternalType(
int nativeProfileSyncServiceAndroid);
private native boolean nativeIsUsingSecondaryPassphrase(int nativeProfileSyncServiceAndroid);
private native boolean nativeSetDecryptionPassphrase(
int nativeProfileSyncServiceAndroid, String passphrase);
private native void nativeSetEncryptionPassphrase(
int nativeProfileSyncServiceAndroid, String passphrase, boolean isGaia);
private native boolean nativeIsCryptographerReady(int nativeProfileSyncServiceAndroid);
private native int nativeGetPassphraseType(int nativeProfileSyncServiceAndroid);
private native boolean nativeHasExplicitPassphraseTime(int nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterGooglePassphraseBodyWithDateText(
int nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterCustomPassphraseBodyWithDateText(
int nativeProfileSyncServiceAndroid);
private native String nativeGetCurrentSignedInAccountText(int nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterCustomPassphraseBodyText(
int nativeProfileSyncServiceAndroid);
private native boolean nativeIsSyncKeystoreMigrationDone(int nativeProfileSyncServiceAndroid);
private native long nativeGetEnabledDataTypes(
int nativeProfileSyncServiceAndroid);
private native void nativeSetPreferredDataTypes(
int nativeProfileSyncServiceAndroid, boolean syncEverything, long modelTypeSelection);
private native void nativeSetSetupInProgress(
int nativeProfileSyncServiceAndroid, boolean inProgress);
private native void nativeSetSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
private native boolean nativeHasSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
private native boolean nativeIsStartSuppressed(int nativeProfileSyncServiceAndroid);
private native boolean nativeHasKeepEverythingSynced(int nativeProfileSyncServiceAndroid);
private native boolean nativeHasUnrecoverableError(int nativeProfileSyncServiceAndroid);
private native String nativeGetAboutInfoForTest(int nativeProfileSyncServiceAndroid);
private native long nativeGetLastSyncedTimeForTest(int nativeProfileSyncServiceAndroid);
}