// 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 org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.components.sync.ModelType;
import org.chromium.components.sync.PassphraseType;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
/**
* JNI wrapper for the native ProfileSyncService.
*
* This class purely makes calls to native and contains absolutely no business logic. It is only
* usable from the UI thread as the native ProfileSyncService requires its access to be on the
* UI thread. See chrome/browser/sync/profile_sync_service.h for more details.
*/
public class ProfileSyncService {
/**
* Listener for the underlying sync status.
*/
public interface SyncStateChangedListener {
// Invoked when the status has changed.
public void syncStateChanged();
}
/**
* Callback for getAllNodes.
*/
public static class GetAllNodesCallback {
private String mNodesString;
// Invoked when getAllNodes completes.
public void onResult(String nodesString) {
mNodesString = nodesString;
}
// Returns the result of GetAllNodes as a JSONArray.
@VisibleForTesting
public JSONArray getNodesAsJsonArray() throws JSONException {
return new JSONArray(mNodesString);
}
}
/**
* Provider for the Android master sync flag.
*/
interface MasterSyncEnabledProvider {
// Returns whether master sync is enabled.
public boolean isMasterSyncEnabled();
}
private static final String TAG = "ProfileSyncService";
private static final int[] ALL_SELECTABLE_TYPES = new int[] {
ModelType.AUTOFILL,
ModelType.BOOKMARKS,
ModelType.PASSWORDS,
ModelType.PREFERENCES,
ModelType.PROXY_TABS,
ModelType.TYPED_URLS
};
private static ProfileSyncService sProfileSyncService;
private static boolean sInitialized = false;
// Sync state changes more often than listeners are added/removed, so using CopyOnWrite.
private final List<SyncStateChangedListener> mListeners =
new CopyOnWriteArrayList<SyncStateChangedListener>();
/**
* Native ProfileSyncServiceAndroid object. Cannot be final because it is initialized in
* {@link init()}.
*/
private long mNativeProfileSyncServiceAndroid;
/**
* An object that knows whether Android's master sync setting is enabled.
*/
private MasterSyncEnabledProvider mMasterSyncEnabledProvider;
/**
* Retrieves or creates the ProfileSyncService singleton instance. Returns null if sync is
* disabled (via flag or variation).
*
* Can only be accessed on the main thread.
*/
@Nullable
@SuppressFBWarnings("LI_LAZY_INIT")
public static ProfileSyncService get() {
ThreadUtils.assertOnUiThread();
if (!sInitialized) {
sProfileSyncService = new ProfileSyncService();
if (sProfileSyncService.mNativeProfileSyncServiceAndroid == 0) {
sProfileSyncService = null;
}
sInitialized = true;
}
return sProfileSyncService;
}
@VisibleForTesting
public static void overrideForTests(ProfileSyncService profileSyncService) {
sProfileSyncService = profileSyncService;
sInitialized = true;
}
protected ProfileSyncService() {
init();
}
/**
* This is called pretty early in our application. Avoid any blocking operations here. init()
* is a separate function to enable a test subclass of ProfileSyncService to completely stub out
* ProfileSyncService.
*/
protected void init() {
ThreadUtils.assertOnUiThread();
// 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 long getProfileSyncServiceAndroid() {
return get().mNativeProfileSyncServiceAndroid;
}
public void signOut() {
nativeSignOutSync(mNativeProfileSyncServiceAndroid);
}
public String querySyncStatus() {
ThreadUtils.assertOnUiThread();
return nativeQuerySyncStatusSummary(mNativeProfileSyncServiceAndroid);
}
/**
* Sets the the machine tag used by session sync.
*/
public void setSessionsId(String sessionTag) {
ThreadUtils.assertOnUiThread();
nativeSetSyncSessionsId(mNativeProfileSyncServiceAndroid, sessionTag);
}
/**
* Returns the actual passphrase type being used for encryption.
* The sync backend must be running (isBackendInitialized() 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, use isPassphraseRequiredForDecryption().
*/
public PassphraseType getPassphraseType() {
assert isBackendInitialized();
int passphraseType = nativeGetPassphraseType(mNativeProfileSyncServiceAndroid);
return PassphraseType.fromInternalValue(passphraseType);
}
/**
* Returns true if the current explicit passphrase time is defined.
*/
public boolean hasExplicitPassphraseTime() {
assert isBackendInitialized();
return nativeHasExplicitPassphraseTime(mNativeProfileSyncServiceAndroid);
}
/**
* Returns the current explicit passphrase time in milliseconds since epoch.
*/
public long getExplicitPassphraseTime() {
assert isBackendInitialized();
return nativeGetExplicitPassphraseTime(mNativeProfileSyncServiceAndroid);
}
public String getSyncEnterGooglePassphraseBodyWithDateText() {
assert isBackendInitialized();
return nativeGetSyncEnterGooglePassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
}
public String getSyncEnterCustomPassphraseBodyWithDateText() {
assert isBackendInitialized();
return nativeGetSyncEnterCustomPassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
}
public String getCurrentSignedInAccountText() {
assert isBackendInitialized();
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
* (isBackendInitialized() returns true) before calling this function.
*
* @return true if sync is using a custom passphrase.
*/
public boolean isUsingSecondaryPassphrase() {
assert isBackendInitialized();
return nativeIsUsingSecondaryPassphrase(mNativeProfileSyncServiceAndroid);
}
public byte[] getCustomPassphraseKey() {
assert isUsingSecondaryPassphrase();
return nativeGetCustomPassphraseKey(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 isBackendInitialized();
return nativeIsPassphraseRequiredForDecryption(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if the sync backend is running.
*
* @return true if sync is initialized/running.
*/
public boolean isBackendInitialized() {
return nativeIsBackendInitialized(mNativeProfileSyncServiceAndroid);
}
/**
* Checks if encrypting all the data types is allowed.
*
* @return true if encrypting all data types is allowed, false if only passwords are allowed to
* be encrypted.
*/
public boolean isEncryptEverythingAllowed() {
assert isBackendInitialized();
return nativeIsEncryptEverythingAllowed(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 isBackendInitialized();
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 isBackendInitialized();
nativeEnableEncryptEverything(mNativeProfileSyncServiceAndroid);
}
public void setEncryptionPassphrase(String passphrase) {
assert isBackendInitialized();
nativeSetEncryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase);
}
public boolean isCryptographerReady() {
assert isBackendInitialized();
return nativeIsCryptographerReady(mNativeProfileSyncServiceAndroid);
}
public boolean setDecryptionPassphrase(String passphrase) {
assert isBackendInitialized();
return nativeSetDecryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase);
}
public GoogleServiceAuthError.State getAuthError() {
int authErrorCode = nativeGetAuthError(mNativeProfileSyncServiceAndroid);
return GoogleServiceAuthError.State.fromCode(authErrorCode);
}
/**
* Gets client action for sync protocol error.
*
* @return {@link ProtocolErrorClientAction}.
*/
public int getProtocolErrorClientAction() {
return nativeGetProtocolErrorClientAction(mNativeProfileSyncServiceAndroid);
}
/**
* Gets the set of data types that are currently syncing.
*
* This is affected by whether sync is on.
*
* @return Set of active data types.
*/
public Set<Integer> getActiveDataTypes() {
int[] activeDataTypes = nativeGetActiveDataTypes(mNativeProfileSyncServiceAndroid);
return modelTypeArrayToSet(activeDataTypes);
}
/**
* Gets the set of data types that are enabled in sync.
*
* This is unaffected by whether sync is on.
*
* @return Set of preferred types.
*/
public Set<Integer> getPreferredDataTypes() {
int[] modelTypeArray = nativeGetPreferredDataTypes(mNativeProfileSyncServiceAndroid);
return modelTypeArrayToSet(modelTypeArray);
}
private static Set<Integer> modelTypeArrayToSet(int[] modelTypeArray) {
Set<Integer> modelTypeSet = new HashSet<Integer>();
for (int i = 0; i < modelTypeArray.length; i++) {
modelTypeSet.add(modelTypeArray[i]);
}
return modelTypeSet;
}
private static int[] modelTypeSetToArray(Set<Integer> modelTypeSet) {
int[] modelTypeArray = new int[modelTypeSet.size()];
int i = 0;
for (int modelType : modelTypeSet) {
modelTypeArray[i++] = modelType;
}
return modelTypeArray;
}
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<Integer> enabledTypes) {
nativeSetPreferredDataTypes(mNativeProfileSyncServiceAndroid, syncEverything, syncEverything
? ALL_SELECTABLE_TYPES : modelTypeSetToArray(enabledTypes));
}
public void setFirstSetupComplete() {
nativeSetFirstSetupComplete(mNativeProfileSyncServiceAndroid);
}
// TODO(maxbogue): Remove when downstream is updated to use the above.
@Deprecated
public void setSyncSetupCompleted() {
nativeSetFirstSetupComplete(mNativeProfileSyncServiceAndroid);
}
public boolean isFirstSetupComplete() {
return nativeIsFirstSetupComplete(mNativeProfileSyncServiceAndroid);
}
public boolean isSyncRequested() {
return nativeIsSyncRequested(mNativeProfileSyncServiceAndroid);
}
// TODO(maxbogue): Remove this annotation once this method is used outside of tests.
@VisibleForTesting
public boolean isSyncActive() {
return nativeIsSyncActive(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() {
for (SyncStateChangedListener listener : mListeners) {
listener.syncStateChanged();
}
}
@VisibleForTesting
public String getSyncInternalsInfoForTest() {
ThreadUtils.assertOnUiThread();
return nativeGetAboutInfoForTest(mNativeProfileSyncServiceAndroid);
}
/**
* Starts the sync engine.
*/
public void requestStart() {
nativeRequestStart(mNativeProfileSyncServiceAndroid);
}
/**
* Stops the sync engine.
*/
public void requestStop() {
nativeRequestStop(mNativeProfileSyncServiceAndroid);
}
/**
* Flushes the sync directory.
*/
public void flushDirectory() {
nativeFlushDirectory(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.
*/
@VisibleForTesting
public long getLastSyncedTimeForTest() {
return nativeGetLastSyncedTimeForTest(mNativeProfileSyncServiceAndroid);
}
/**
* Overrides the Sync engine's NetworkResources. This is used to set up the Sync FakeServer for
* testing.
*
* @param networkResources the pointer to the NetworkResources created by the native code. It
* is assumed that the Java caller has ownership of this pointer;
* ownership is transferred as part of this call.
*/
@VisibleForTesting
public void overrideNetworkResourcesForTest(long networkResources) {
nativeOverrideNetworkResourcesForTest(mNativeProfileSyncServiceAndroid, networkResources);
}
/**
* Returns whether this client has previously prompted the user for a
* passphrase error via the android system notifications.
*
* Can be called whether or not sync is initialized.
*
* @return Whether client has prompted for a passphrase error previously.
*/
public boolean isPassphrasePrompted() {
return nativeIsPassphrasePrompted(mNativeProfileSyncServiceAndroid);
}
/**
* Sets whether this client has previously prompted the user for a
* passphrase error via the android system notifications.
*
* Can be called whether or not sync is initialized.
*
* @param prompted whether the client has prompted the user previously.
*/
public void setPassphrasePrompted(boolean prompted) {
nativeSetPassphrasePrompted(mNativeProfileSyncServiceAndroid,
prompted);
}
/**
* Set the MasterSyncEnabledProvider for ProfileSyncService.
*
* This method is intentionally package-scope and should only be called once.
*/
void setMasterSyncEnabledProvider(MasterSyncEnabledProvider masterSyncEnabledProvider) {
ThreadUtils.assertOnUiThread();
assert mMasterSyncEnabledProvider == null;
mMasterSyncEnabledProvider = masterSyncEnabledProvider;
}
/**
* Returns whether Android's master sync setting is enabled.
*/
@CalledByNative
public boolean isMasterSyncEnabled() {
ThreadUtils.assertOnUiThread();
// TODO(maxbogue): ensure that this method is never called before
// setMasterSyncEnabledProvider() and change the line below to an assert.
// See http://crbug.com/570569
if (mMasterSyncEnabledProvider == null) return true;
return mMasterSyncEnabledProvider.isMasterSyncEnabled();
}
/**
* Invokes the onResult method of the callback from native code.
*/
@CalledByNative
private static void onGetAllNodesResult(GetAllNodesCallback callback, String nodes) {
callback.onResult(nodes);
}
/**
* Retrieves a JSON version of local Sync data via the native GetAllNodes method.
* This method is asynchronous; the result will be sent to the callback.
*/
@VisibleForTesting
public void getAllNodes(GetAllNodesCallback callback) {
nativeGetAllNodes(mNativeProfileSyncServiceAndroid, callback);
}
// Native methods
private native long nativeInit();
private native void nativeRequestStart(long nativeProfileSyncServiceAndroid);
private native void nativeRequestStop(long nativeProfileSyncServiceAndroid);
private native void nativeFlushDirectory(long nativeProfileSyncServiceAndroid);
private native void nativeSignOutSync(long nativeProfileSyncServiceAndroid);
private native void nativeSetSyncSessionsId(long nativeProfileSyncServiceAndroid, String tag);
private native String nativeQuerySyncStatusSummary(long nativeProfileSyncServiceAndroid);
private native int nativeGetAuthError(long nativeProfileSyncServiceAndroid);
private native int nativeGetProtocolErrorClientAction(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsBackendInitialized(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsEncryptEverythingAllowed(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsEncryptEverythingEnabled(long nativeProfileSyncServiceAndroid);
private native void nativeEnableEncryptEverything(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsPassphraseRequiredForDecryption(
long nativeProfileSyncServiceAndroid);
private native boolean nativeIsUsingSecondaryPassphrase(long nativeProfileSyncServiceAndroid);
private native byte[] nativeGetCustomPassphraseKey(long nativeProfileSyncServiceAndroid);
private native boolean nativeSetDecryptionPassphrase(
long nativeProfileSyncServiceAndroid, String passphrase);
private native void nativeSetEncryptionPassphrase(
long nativeProfileSyncServiceAndroid, String passphrase);
private native boolean nativeIsCryptographerReady(long nativeProfileSyncServiceAndroid);
private native int nativeGetPassphraseType(long nativeProfileSyncServiceAndroid);
private native boolean nativeHasExplicitPassphraseTime(long nativeProfileSyncServiceAndroid);
private native long nativeGetExplicitPassphraseTime(long nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterGooglePassphraseBodyWithDateText(
long nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterCustomPassphraseBodyWithDateText(
long nativeProfileSyncServiceAndroid);
private native String nativeGetCurrentSignedInAccountText(long nativeProfileSyncServiceAndroid);
private native String nativeGetSyncEnterCustomPassphraseBodyText(
long nativeProfileSyncServiceAndroid);
private native int[] nativeGetActiveDataTypes(long nativeProfileSyncServiceAndroid);
private native int[] nativeGetPreferredDataTypes(long nativeProfileSyncServiceAndroid);
private native void nativeSetPreferredDataTypes(
long nativeProfileSyncServiceAndroid, boolean syncEverything, int[] modelTypeArray);
private native void nativeSetSetupInProgress(
long nativeProfileSyncServiceAndroid, boolean inProgress);
private native void nativeSetFirstSetupComplete(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsFirstSetupComplete(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsSyncRequested(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsSyncActive(long nativeProfileSyncServiceAndroid);
private native boolean nativeHasKeepEverythingSynced(long nativeProfileSyncServiceAndroid);
private native boolean nativeHasUnrecoverableError(long nativeProfileSyncServiceAndroid);
private native boolean nativeIsPassphrasePrompted(long nativeProfileSyncServiceAndroid);
private native void nativeSetPassphrasePrompted(long nativeProfileSyncServiceAndroid,
boolean prompted);
private native String nativeGetAboutInfoForTest(long nativeProfileSyncServiceAndroid);
private native long nativeGetLastSyncedTimeForTest(long nativeProfileSyncServiceAndroid);
private native void nativeOverrideNetworkResourcesForTest(
long nativeProfileSyncServiceAndroid, long networkResources);
private native void nativeGetAllNodes(
long nativeProfileSyncServiceAndroid, GetAllNodesCallback callback);
}