blob: 2f9d56615604f3aa0ee9212ec719aa8619143ac2 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.sdksandbox;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import android.annotation.Nullable;
import android.app.sdksandbox.testutils.StubSdkSandboxManagerService;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.test.InstrumentationRegistry;
import com.android.internal.annotations.GuardedBy;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/** Tests {@link SharedPreferencesSyncManager} APIs. */
@RunWith(JUnit4.class)
public class SharedPreferencesSyncManagerUnitTest {
private static final String KEY_TO_UPDATE = "hello1";
private static final SharedPreferencesKey KEY_WITH_TYPE_TO_UPDATE =
new SharedPreferencesKey(KEY_TO_UPDATE, SharedPreferencesKey.KEY_TYPE_STRING);
private static final Map<String, String> TEST_DATA =
Map.of(KEY_TO_UPDATE, "world1", "hello2", "world2", "empty", "");
private static final Set<SharedPreferencesKey> KEYS_TO_SYNC =
Set.of(
new SharedPreferencesKey(KEY_TO_UPDATE, SharedPreferencesKey.KEY_TYPE_STRING),
new SharedPreferencesKey("hello2", SharedPreferencesKey.KEY_TYPE_STRING),
new SharedPreferencesKey("empty", SharedPreferencesKey.KEY_TYPE_STRING));
private static final int INTERNAL_ERROR_CODE = ISharedPreferencesSyncCallback.INTERNAL_ERROR;
private static final String INTERNAL_ERROR_MSG = "Some error occurred";
private static final int SANDBOX_NOT_AVAILABLE_ERROR_CODE =
ISharedPreferencesSyncCallback.SANDBOX_NOT_AVAILABLE;
private static final String SANDBOX_NOT_AVAILABLE_ERROR_MSG = "Sandbox has not started yet";
private SharedPreferencesSyncManager mSyncManager;
private FakeSdkSandboxManagerService mSdkSandboxManagerService;
private Context mContext;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
mSdkSandboxManagerService = new FakeSdkSandboxManagerService();
mSyncManager = new SharedPreferencesSyncManager(mContext, mSdkSandboxManagerService);
}
@After
public void tearDown() throws Exception {
getDefaultSharedPreferences().edit().clear().commit();
}
@Test
public void test_sharedPreferencesSyncManager_isSingleton() throws Exception {
final SharedPreferencesSyncManager manager1 =
SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
final SharedPreferencesSyncManager manager2 =
SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
assertThat(manager1).isSameInstanceAs(manager2);
}
@Test
public void test_addSyncKeys_isIncremental() throws Exception {
// Add one key
final SharedPreferencesKey foo =
new SharedPreferencesKey("foo", SharedPreferencesKey.KEY_TYPE_STRING);
mSyncManager.addSharedPreferencesSyncKeys(Set.of(foo));
assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly(foo);
// Add another key
final SharedPreferencesKey bar =
new SharedPreferencesKey("bar", SharedPreferencesKey.KEY_TYPE_STRING);
mSyncManager.addSharedPreferencesSyncKeys(Set.of(bar));
assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly(foo, bar);
}
@Test
public void test_addSyncKeys_isIncremental_newOverwritesOld() throws Exception {
// Add one key
final SharedPreferencesKey fooStr =
new SharedPreferencesKey("foo", SharedPreferencesKey.KEY_TYPE_STRING);
mSyncManager.addSharedPreferencesSyncKeys(Set.of(fooStr));
assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly(fooStr);
// Add another key
final SharedPreferencesKey fooInt =
new SharedPreferencesKey("foo", SharedPreferencesKey.KEY_TYPE_INTEGER);
mSyncManager.addSharedPreferencesSyncKeys(Set.of(fooInt));
assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly(fooInt);
}
@Test
public void test_bulkSync_syncSpecifiedKeys() throws Exception {
// Populate default shared preference with test data
populateDefaultSharedPreference(TEST_DATA);
// Add specific shared keys that we want to sync
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Verify that sync manager passes the correct data to SdkSandboxManager
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(mSdkSandboxManagerService.getCallingPackageName())
.isEqualTo(mContext.getPackageName());
assertThat(capturedData.keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
for (String key : TEST_DATA.keySet()) {
assertThat(capturedData.getString(key)).isEqualTo(TEST_DATA.get(key));
}
}
@Test
public void test_bulkSync_syncMissingKeys() throws Exception {
// Add specific shared keys that we want to sync
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Verify that sync manager passes empty value for missing keys
final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
assertThat(update.getKeysInUpdate()).containsExactlyElementsIn(KEYS_TO_SYNC);
assertThat(update.getData().keySet()).isEmpty();
}
@Test
public void test_bulkSync_ignoreUnspecifiedKeys() throws Exception {
// Populate default shared preference and set specific keys for sycing
populateDefaultSharedPreference(TEST_DATA);
// Populate extra data outside of shared key list
populateDefaultSharedPreference(Map.of("extraKey", "notSpecifiedByApi"));
// Set specific shared keys that we want to sync
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Verify that sync manager passes the correct data to SdkSandboxManager
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(capturedData.keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
}
@Test
public void test_bulkSync_ignoreValueOfWrongType() throws Exception {
// Update key with a wrong type
getDefaultSharedPreferences().edit().putFloat(KEY_TO_UPDATE, 1.0f).commit();
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Verify that sync manager ignores wrong type
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(capturedData.keySet()).doesNotContain(KEY_TO_UPDATE);
}
@Test
public void test_bulkSync_supportsAllTypesOfValues() throws Exception {
// Populate default shared preference with all valid types
final SharedPreferences pref = getDefaultSharedPreferences();
final SharedPreferences.Editor editor = pref.edit();
editor.putString("string", "value");
editor.putBoolean("boolean", true);
editor.putFloat("float", 1.2f);
editor.putInt("int", 1);
editor.putLong("long", 1L);
editor.putStringSet("set", Set.of("value"));
editor.commit();
// Set keys to sync and then sync data
final Set<SharedPreferencesKey> keysToSync =
Set.of(
new SharedPreferencesKey("string", SharedPreferencesKey.KEY_TYPE_STRING),
new SharedPreferencesKey("boolean", SharedPreferencesKey.KEY_TYPE_BOOLEAN),
new SharedPreferencesKey("float", SharedPreferencesKey.KEY_TYPE_FLOAT),
new SharedPreferencesKey("int", SharedPreferencesKey.KEY_TYPE_INTEGER),
new SharedPreferencesKey("long", SharedPreferencesKey.KEY_TYPE_LONG),
new SharedPreferencesKey("set", SharedPreferencesKey.KEY_TYPE_STRING_SET));
mSyncManager.addSharedPreferencesSyncKeys(keysToSync);
// Verify that sync manager passes the correct data to SdkSandboxManager
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(capturedData.keySet()).hasSize(6);
assertThat(capturedData.getString("string")).isEqualTo(pref.getString("string", ""));
assertThat(capturedData.getBoolean("boolean")).isEqualTo(pref.getBoolean("boolean", false));
assertThat(capturedData.getFloat("float")).isEqualTo(pref.getFloat("float", 0.0f));
assertThat(capturedData.getInt("int")).isEqualTo(pref.getInt("int", 0));
assertThat(capturedData.getLong("long")).isEqualTo(pref.getLong("long", 0L));
assertThat(capturedData.getStringArrayList("set"))
.containsExactlyElementsIn(pref.getStringSet("set", Collections.emptySet()));
}
@Test
public void test_updateListener_syncsFurtherUpdates() throws Exception {
// Set specified keys for sycing and register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
// Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
mSdkSandboxManagerService.blockForReceivingUpdates(2);
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(capturedData.keySet()).containsExactly(KEY_TO_UPDATE);
assertThat(capturedData.getString(KEY_TO_UPDATE)).isEqualTo("update");
}
@Test
public void test_updateListener_ignoresUnspecifiedKeys() throws Exception {
// Set specified keys for sycing and register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
getDefaultSharedPreferences().edit().putString("unspecified_key", "update").commit();
// Verify SdkSandboxManagerService does not receive the update for unspecified key
Thread.sleep(5000);
assertThat(mSdkSandboxManagerService.getNumberOfUpdatesReceived()).isEqualTo(1);
}
@Test
public void test_updateListener_supportsAllTypesOfValues() throws Exception {
// Set keys to sync and then sync data to register listener
final Set<SharedPreferencesKey> keysToSync =
Set.of(
new SharedPreferencesKey("string", SharedPreferencesKey.KEY_TYPE_STRING),
new SharedPreferencesKey("boolean", SharedPreferencesKey.KEY_TYPE_BOOLEAN),
new SharedPreferencesKey("float", SharedPreferencesKey.KEY_TYPE_FLOAT),
new SharedPreferencesKey("int", SharedPreferencesKey.KEY_TYPE_INTEGER),
new SharedPreferencesKey("long", SharedPreferencesKey.KEY_TYPE_LONG),
new SharedPreferencesKey("set", SharedPreferencesKey.KEY_TYPE_STRING_SET));
mSyncManager.addSharedPreferencesSyncKeys(keysToSync);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Clear the bulk update for ease of reasoning
mSdkSandboxManagerService.clearUpdates();
// Update the shared preference
final SharedPreferences pref = getDefaultSharedPreferences();
final SharedPreferences.Editor editor = pref.edit();
editor.putString("string", "value");
editor.putBoolean("boolean", true);
editor.putFloat("float", 1.2f);
editor.putInt("int", 1);
editor.putLong("long", 1L);
editor.putStringSet("set", Set.of("value"));
editor.commit();
// Verify that sync manager receives one bundle for each key update
mSdkSandboxManagerService.blockForReceivingUpdates(6);
final ArrayList<SharedPreferencesUpdate> allUpdates =
mSdkSandboxManagerService.getAllUpdates();
assertThat(allUpdates).hasSize(6);
for (SharedPreferencesUpdate update : allUpdates) {
final Bundle data = update.getData();
assertThat(data.keySet()).hasSize(1);
final String key = data.keySet().toArray()[0].toString();
if (key.equals("string")) {
assertThat(data.getString(key)).isEqualTo(pref.getString(key, ""));
} else if (key.equals("boolean")) {
assertThat(data.getBoolean(key)).isEqualTo(pref.getBoolean(key, false));
} else if (key.equals("float")) {
assertThat(data.getFloat(key)).isEqualTo(pref.getFloat(key, 0.0f));
} else if (key.equals("int")) {
assertThat(data.getInt(key)).isEqualTo(pref.getInt(key, 0));
} else if (key.equals("long")) {
assertThat(data.getLong(key)).isEqualTo(pref.getLong(key, 0L));
} else if (key.equals("set")) {
assertThat(data.getStringArrayList(key))
.containsExactlyElementsIn(pref.getStringSet(key, Collections.emptySet()));
} else {
fail("Unknown key found");
}
}
}
/** Test that we can handle removal of keys */
@Test
public void test_updateListener_removeKey() throws Exception {
populateDefaultSharedPreference(TEST_DATA);
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
getDefaultSharedPreferences().edit().remove(KEY_TO_UPDATE).commit();
// Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
mSdkSandboxManagerService.blockForReceivingUpdates(2);
final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
assertThat(update.getData().keySet()).doesNotContain(KEY_TO_UPDATE);
assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
}
/** Test that we can handle removal of keys by putting null */
@Test
public void test_updateListener_putNullValueForKey() throws Exception {
populateDefaultSharedPreference(TEST_DATA);
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, null).commit();
// Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
mSdkSandboxManagerService.blockForReceivingUpdates(2);
final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
assertThat(update.getData().keySet()).doesNotContain(KEY_TO_UPDATE);
assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
}
@Test
public void test_updateListener_removeAllKeys() throws Exception {
populateDefaultSharedPreference(TEST_DATA);
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Clear all keys
getDefaultSharedPreferences().edit().clear().commit();
// Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
mSdkSandboxManagerService.blockForReceivingUpdates(2);
final SharedPreferencesUpdate lastUpdate = mSdkSandboxManagerService.getLastUpdate();
assertThat(lastUpdate.getData().keySet()).isEmpty();
assertThat(lastUpdate.getKeysInUpdate()).containsExactlyElementsIn(KEYS_TO_SYNC);
}
@Test
public void test_updateListener_multipleCalls_updateListenerRegisteredOnce() throws Exception {
// Add keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Verify updating SharedPreferences results in only one update
mSdkSandboxManagerService.clearUpdates(); // For cleaner observation
// Update the SharedPreference to trigger listeners
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
// Only one update should be received
assertThrows(
TimeoutException.class,
() -> mSdkSandboxManagerService.blockForReceivingUpdates(2));
}
// TODO(b/239403323): When all keys are removed, update listener should not update
// TODO(b/239403323): Sync should just log errors and continue syncing.
@Test
public void test_onError_bulksync_canBeRestarted() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Report an error via the callback
mSdkSandboxManagerService
.getLastCallback()
.onError(INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG);
// Verify we can restart the sync
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
}
@Ignore("b/239403323: Get rid of internal errors, since we are not notifying user about it")
@Test
public void test_onError_bulksync_stopsOnInternalError() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Report an error via the callback
mSdkSandboxManagerService
.getLastCallback()
.onError(INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG);
// Verify that sync is still running
assertThatSyncIsRunning();
}
// TODO(b/239403323): Verify behavior when in waiting state
/** Test that we support starting sync before sandbox is created */
@Ignore("b/239403323: Make update listener registration independent of bulk sync")
@Test
public void test_onError_bulksync_SandboxNotAvailableError() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Report sandbox has not been created
mSdkSandboxManagerService
.getLastCallback()
.onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
// Verify that sync was still running
assertThatSyncIsRunning();
assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
}
@Test
public void test_onError_updateListener_canBeRestarted() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
mSdkSandboxManagerService.clearUpdates(); // For ease of reasoning
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
// Wait until update is received
mSdkSandboxManagerService.blockForReceivingUpdates(1);
// Report an error via the callback
mSdkSandboxManagerService
.getLastCallback()
.onError(INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG);
// Verify we can restart the sync
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
}
/** Test that we support starting sync before sandbox is created */
@Test
public void test_onError_updateListener_stopsOnSandboxNotAvailableError() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Update the SharedPreference to trigger listeners
mSdkSandboxManagerService.clearUpdates(); // For ease of reasoning
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
// Wait until update is received
mSdkSandboxManagerService.blockForReceivingUpdates(1);
// Report an error via the callback
mSdkSandboxManagerService
.getLastCallback()
.onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
// Verify that sync is in waiting state now
assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
}
@Test
public void test_onError_updateListener_notRegisteredWhenWaitingForSandbox() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
mSdkSandboxManagerService.getLastCallback().onSuccess();
// Send SyncManager to waiting state
mSdkSandboxManagerService
.getLastCallback()
.onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
// Update the SharedPreference to trigger listeners
mSdkSandboxManagerService.clearUpdates(); // For ease of reasoning
getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
// Verify update not received
assertThrows(
TimeoutException.class,
() -> mSdkSandboxManagerService.blockForReceivingUpdates(1));
}
@Test
public void test_onSandboxStart_bulkSyncRetries() throws Exception {
// Set keys to sync and then sync data to register listener
mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
// Send SyncManager to waiting state
mSdkSandboxManagerService
.getLastCallback()
.onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
// Notify syncmanager eventually when sandbox starts
final ISharedPreferencesSyncCallback firstCallback =
mSdkSandboxManagerService.getLastCallback();
mSdkSandboxManagerService.getLastCallback().onSandboxStart();
// Verify another bulk sync update is sent to SdkSandboxManagerService
mSdkSandboxManagerService.blockForReceivingUpdates(2);
// Notify again, but this time it should not trigger a new update since we were not waiting.
mSdkSandboxManagerService.getLastCallback().onSandboxStart();
firstCallback.onSandboxStart();
assertThrows(
TimeoutException.class,
() -> mSdkSandboxManagerService.blockForReceivingUpdates(3));
}
/** Write all key-values provided in the map to app's default SharedPreferences */
private void populateDefaultSharedPreference(Map<String, String> data) {
final SharedPreferences.Editor editor = getDefaultSharedPreferences().edit();
for (Map.Entry<String, String> entry : data.entrySet()) {
editor.putString(entry.getKey(), entry.getValue());
}
editor.apply();
}
private SharedPreferences getDefaultSharedPreferences() {
final Context appContext = mContext.getApplicationContext();
return PreferenceManager.getDefaultSharedPreferences(appContext);
}
private void assertThatSyncIsRunning() throws Exception {
// There must be some keys in the pool for sync to be running
final Set<SharedPreferencesKey> syncKeys = mSyncManager.getSharedPreferencesSyncKeys();
assertThat(syncKeys).isNotEmpty();
// If sync is still active, updating any key would result in syncing date
mSdkSandboxManagerService.clearUpdates(); // For easier assert
final SharedPreferencesKey syncKey = syncKeys.iterator().next();
assertThat(syncKey.getType()).isEqualTo(SharedPreferencesKey.KEY_TYPE_STRING);
getDefaultSharedPreferences()
.edit()
.putString(syncKey.getName(), "assertSyncRunning")
.commit();
// Verify that SyncManager tried to sync
mSdkSandboxManagerService.blockForReceivingUpdates(1);
final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
assertThat(capturedData.keySet()).containsExactly(syncKey);
assertThat(capturedData.getString(KEY_TO_UPDATE)).isEqualTo("assertSyncIsRunning");
}
private static class FakeSdkSandboxManagerService extends StubSdkSandboxManagerService {
@GuardedBy("this")
private ArrayList<SharedPreferencesUpdate> mUpdateCache = new ArrayList<>();
@GuardedBy("this")
private ISharedPreferencesSyncCallback mLastCallback = null;
@GuardedBy("this")
private String mCallingPackageName = null;
/** Gets updated when {@link blockForReceivingUpdates} is called. */
private CountDownLatch mWaitForMoreUpdates = new CountDownLatch(0);
@Override
public synchronized void syncDataFromClient(
String callingPackageName,
long timeAppCalledSystemServer,
SharedPreferencesUpdate update,
ISharedPreferencesSyncCallback callback) {
if (mCallingPackageName == null) {
mCallingPackageName = callingPackageName;
} else {
assertThat(mCallingPackageName).isEqualTo(callingPackageName);
}
mUpdateCache.add(update);
mLastCallback = callback;
mWaitForMoreUpdates.countDown();
}
public synchronized String getCallingPackageName() {
return mCallingPackageName;
}
@Nullable
public synchronized SharedPreferencesUpdate getLastUpdate() {
if (mUpdateCache.isEmpty()) {
throw new AssertionError(
"Fake SdkSandboxManagerService did not receive any update");
}
return mUpdateCache.get(mUpdateCache.size() - 1);
}
@Nullable
public synchronized ISharedPreferencesSyncCallback getLastCallback() {
return mLastCallback;
}
public synchronized ArrayList<SharedPreferencesUpdate> getAllUpdates() {
return new ArrayList<>(mUpdateCache);
}
public synchronized int getNumberOfUpdatesReceived() {
return mUpdateCache.size();
}
public synchronized void clearUpdates() {
mUpdateCache.clear();
}
public void blockForReceivingUpdates(int numberOfUpdates) throws Exception {
synchronized (this) {
final int updatesNeeded = numberOfUpdates - getNumberOfUpdatesReceived();
if (updatesNeeded <= 0) {
return;
}
mWaitForMoreUpdates = new CountDownLatch(updatesNeeded);
}
if (!mWaitForMoreUpdates.await(5000, TimeUnit.MILLISECONDS)) {
throw new TimeoutException(
"Failed to receive required number of updates. Required: "
+ numberOfUpdates
+ ", but found: "
+ getNumberOfUpdatesReceived());
}
}
}
}