// Copyright 2014 The Chromium Authors
// 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 androidx.annotation.Nullable;
import com.google.protobuf.InvalidProtocolBufferException;
import org.jni_zero.NativeMethods;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.components.sync.protocol.EntitySpecifics;
import org.chromium.components.sync.protocol.SyncEntity;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.List;
/** Assists in Java interaction the native Sync FakeServer. Can be used from any thread. */
public class FakeServerHelper {
private static final String TAG = "FakeServerHelper";
// Singleton instance. Set on every createInstanceAndGet() and reset to null on every destroy().
// This must be assigned on the UI thread.
private static FakeServerHelper sFakeServerHelper;
// Must be used from the UI thread.
private final long mNativeFakeServer;
/**
* Creates the singleton FakeServerHelper and returns it. destroyInstance() must be called when
* done to prevent the native object from leaking. If this is called before the previous
* instance is destroyed, it will return null (just to avoid throwing ExecutionException).
* TODO(crbug.com/41451146): When refactoring this method, throw an exception instead of
* returning null.
*/
public static @Nullable FakeServerHelper createInstanceAndGet() {
return ThreadUtils.runOnUiThreadBlocking(
() -> {
if (sFakeServerHelper == null) {
sFakeServerHelper = new FakeServerHelper();
return sFakeServerHelper;
}
Log.w(
TAG,
"destroyInstance() must be called before another FakeServerHelper is"
+ " created");
return null;
});
}
/** Deletes the existing FakeServer if any. */
public static void destroyInstance() {
ThreadUtils.runOnUiThreadBlocking(
() -> {
if (sFakeServerHelper == null) return;
FakeServerHelperJni.get().deleteFakeServer(sFakeServerHelper.mNativeFakeServer);
sFakeServerHelper = null;
});
}
private FakeServerHelper() {
ThreadUtils.assertOnUiThread();
mNativeFakeServer = FakeServerHelperJni.get().createFakeServer();
assert mNativeFakeServer != 0L;
}
/**
* Returns whether {@code count} entities exist on the fake Sync server with the given {@code
* dataType} and {@code name}.
*
* @param count the number of fake server entities to verify
* @param dataType the data type of entities to verify
* @param name the name of entities to verify
* @return whether the number of specified entities exist
*/
public boolean verifyEntityCountByTypeAndName(
final int count, final int dataType, final String name) {
return ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.verifyEntityCountByTypeAndName(
mNativeFakeServer, count, dataType, name));
}
/**
* Verifies whether the sessions on the fake Sync server match the given set of urls.
*
* @param urls the set of urls to check against; order does not matter.
* @return whether the sessions on the server match the given urls.
*/
public boolean verifySessions(final String[] urls) {
return ThreadUtils.runOnUiThreadBlocking(
() -> FakeServerHelperJni.get().verifySessions(mNativeFakeServer, urls));
}
/**
* Returns all the SyncEntities on the fake server with the given dataType.
*
* @param dataType the type of entities to return.
* @return a list of all the SyncEntity protos for that type.
*/
public List<SyncEntity> getSyncEntitiesByDataType(final int dataType)
throws InvalidProtocolBufferException {
byte[][] serializedEntities =
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.getSyncEntitiesByDataType(mNativeFakeServer, dataType));
List<SyncEntity> entities = new ArrayList<SyncEntity>(serializedEntities.length);
for (byte[] serializedEntity : serializedEntities) {
entities.add(SyncEntity.parseFrom(serializedEntity));
}
return entities;
}
/**
* Injects an entity into the fake Sync server. This method only works for entities that will
* eventually contain a unique client tag (e.g., preferences, typed URLs).
*
* @param nonUniqueName the human-readable name for the entity. This value will be used for the
* SyncEntity.name value
* @param clientTag the ID that makes this entity unique across clients. This value will be used
* in hashed form in SyncEntity.server_defined_unique_tag
* @param entitySpecifics the EntitySpecifics proto that represents the entity to inject
*/
public void injectUniqueClientEntity(
final String nonUniqueName,
final String clientTag,
final EntitySpecifics entitySpecifics) {
// The protocol buffer is serialized as a byte array because it can be easily
// deserialized from this format in native code.
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.injectUniqueClientEntity(
mNativeFakeServer,
nonUniqueName,
clientTag,
entitySpecifics.toByteArray()));
}
/**
* Sets the Wallet card and address data to be served in following GetUpdates requests. Note
* that (opposed to the native implementation) this currently only accepts a single entity,
* because that's all we needed so far.
*
* @param entity the SyncEntity to serve for Wallet.
*/
public void setWalletData(final SyncEntity entity) {
// The protocol buffer is serialized as a byte array because it can be easily
// deserialized from this format in native code.
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.setWalletData(mNativeFakeServer, entity.toByteArray()));
}
/**
* Modify the specifics of an entity on the fake Sync server.
*
* @param id the ID of the entity whose specifics to modify
* @param entitySpecifics the new specifics proto for the entity
*/
public void modifyEntitySpecifics(final String id, final EntitySpecifics entitySpecifics) {
// The protocol buffer is serialized as a byte array because it can be easily
// deserialized from this format in native code.
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.modifyEntitySpecifics(
mNativeFakeServer, id, entitySpecifics.toByteArray()));
}
/**
* Injects a device info entity into the fake Sync server.
*
* @param cacheGuid The cache GUID of the entry to inject.
* @param clientName The client name of the entry to inject.
* @param creationTimestamp The timestamp when the entry was created.
* @param lastUpdatedTimestamp The timestamp when the entry was last updated.
*/
public void injectDeviceInfoEntity(
String cacheGuid,
String clientName,
long creationTimestamp,
long lastUpdatedTimestamp) {
ThreadUtils.runOnUiThreadBlocking(
() -> {
FakeServerHelperJni.get()
.injectDeviceInfoEntity(
mNativeFakeServer,
cacheGuid,
clientName,
creationTimestamp,
lastUpdatedTimestamp);
});
}
/**
* Injects a bookmark into the fake Sync server.
*
* @param title the title of the bookmark to inject
* @param url the URL of the bookmark to inject. This String will be passed to the native GURL
* class, so it must be a valid URL under its definition.
* @param parentId the ID of the desired parent bookmark folder
* @param parentGuid the GUID of the desired parent bookmark folder
*/
public void injectBookmarkEntity(
final String title, final GURL url, final String parentId, final String parentGuid) {
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.injectBookmarkEntity(
mNativeFakeServer, title, url, parentId, parentGuid));
}
/**
* Injects a bookmark folder into the fake Sync server.
*
* @param title the title of the bookmark folder to inject
* @param parentId the ID of the desired parent bookmark folder
* @param parentGuid the GUID of the desired parent bookmark folder
*/
public void injectBookmarkFolderEntity(
final String title, final String parentId, final String parentGuid) {
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.injectBookmarkFolderEntity(
mNativeFakeServer, title, parentId, parentGuid));
}
/**
* Modifies an existing bookmark on the fake Sync server.
*
* @param bookmarkId the ID of the bookmark to modify
* @param bookmarkGuid the GUID of the bookmark to modify
* @param title the new title of the bookmark
* @param url the new URL of the bookmark. This String will be passed to the native GURL class,
* so it must be a valid URL under its definition.
* @param parentId the ID of the new desired parent bookmark folder
* @param parentGuid the GUID of the new desired parent bookmark folder
*/
public void modifyBookmarkEntity(
final String bookmarkId,
final String bookmarkGuid,
final String title,
final GURL url,
final String parentId,
final String parentGuid) {
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.modifyBookmarkEntity(
mNativeFakeServer,
bookmarkId,
bookmarkGuid,
title,
url,
parentId,
parentGuid));
}
/**
* Modifies an existing bookmark folder on the fake Sync server.
*
* @param folderId the ID of the bookmark folder to modify
* @param folderGuid the GUID of the bookmark folder to modify
* @param title the new title of the bookmark folder
* @param parentId the ID of the new desired parent bookmark folder
* @param parentGuid the GUID of the new desired parent bookmark folder
*/
public void modifyBookmarkFolderEntity(
final String folderId,
final String folderGuid,
final String title,
final String parentId,
final String parentGuid) {
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.modifyBookmarkFolderEntity(
mNativeFakeServer,
folderId,
folderGuid,
title,
parentId,
parentGuid));
}
/**
* Deletes an entity on the fake Sync server.
*
* <p>In other words, this method injects a tombstone into the fake Sync server.
*
* @param id the server ID of the entity to delete
* @param clientTagHash the client defined unique tag hash of the entity to delete (or an empty
* string if sync does not care about this being a hash)
*/
public void deleteEntity(final String id) {
deleteEntity(id, "");
}
public void deleteEntity(final String id, final String clientTagHash) {
ThreadUtils.runOnUiThreadBlocking(
() -> FakeServerHelperJni.get().deleteEntity(mNativeFakeServer, id, clientTagHash));
}
/**
* Returns the ID of the Bookmark Bar. This value is to be used in conjunction with
* injectBookmarkEntity.
*
* @return the opaque ID of the bookmark bar entity stored in the server
*/
public String getBookmarkBarFolderId() {
return ThreadUtils.runOnUiThreadBlocking(
() -> FakeServerHelperJni.get().getBookmarkBarFolderId(mNativeFakeServer));
}
/**
* Sets a custom passphrase nigori.
*
* @param passphrase the plaintext custom passphrase to set.
*/
public void setCustomPassphraseNigori(String passphrase) {
ThreadUtils.runOnUiThreadBlocking(
() -> {
FakeServerHelperJni.get()
.setCustomPassphraseNigori(mNativeFakeServer, passphrase);
});
}
/** Sets trusted vault nigori with keys derived from trustedVaultKey on the server. */
public void setTrustedVaultNigori(byte[] trustedVaultKey) {
ThreadUtils.runOnUiThreadBlocking(
() ->
FakeServerHelperJni.get()
.setTrustedVaultNigori(mNativeFakeServer, trustedVaultKey));
}
/** Clear the server data (perform dashboard stop and clear). */
public void clearServerData() {
ThreadUtils.runOnUiThreadBlocking(
() -> FakeServerHelperJni.get().clearServerData(mNativeFakeServer));
}
@NativeMethods
interface Natives {
long createFakeServer();
void deleteFakeServer(long fakeServer);
boolean verifyEntityCountByTypeAndName(
long fakeServer, int count, int dataType, String name);
boolean verifySessions(long fakeServer, String[] urlArray);
byte[][] getSyncEntitiesByDataType(long fakeServer, int dataType);
void injectUniqueClientEntity(
long fakeServer,
String nonUniqueName,
String clientTag,
byte[] serializedEntitySpecifics);
void setWalletData(long fakeServer, byte[] serializedEntity);
void modifyEntitySpecifics(long fakeServer, String id, byte[] serializedEntitySpecifics);
void injectDeviceInfoEntity(
long fakeServer,
String cacheGuid,
String clientName,
long creationTimestamp,
long lastUpdatedTimestamp);
void injectBookmarkEntity(
long fakeServer, String title, GURL url, String parentId, String parentGuid);
void injectBookmarkFolderEntity(
long fakeServer, String title, String parentId, String parentGuid);
void modifyBookmarkEntity(
long fakeServer,
String bookmarkId,
String bookmarkGuid,
String title,
GURL url,
String parentId,
String parentGuid);
void modifyBookmarkFolderEntity(
long fakeServer,
String bookmarkId,
String bookmarkGuid,
String title,
String parentId,
String parentGuid);
String getBookmarkBarFolderId(long fakeServer);
void deleteEntity(long fakeServer, String id, String clientDefinedUniqueTag);
void setCustomPassphraseNigori(long fakeServer, String passphrase);
void setTrustedVaultNigori(long fakeServer, byte[] trustedVaultKey);
void clearServerData(long fakeServer);
}
}