• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.sdksandbox;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertThrows;
22 import static org.junit.Assert.fail;
23 
24 import android.annotation.Nullable;
25 import android.app.sdksandbox.testutils.StubSdkSandboxManagerService;
26 import android.content.Context;
27 import android.content.SharedPreferences;
28 import android.os.Bundle;
29 import android.preference.PreferenceManager;
30 
31 import androidx.test.InstrumentationRegistry;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.server.sdksandbox.DeviceSupportedBaseTest;
35 
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.junit.runners.JUnit4;
41 import org.mockito.Mockito;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.concurrent.CountDownLatch;
48 import java.util.concurrent.TimeUnit;
49 import java.util.concurrent.TimeoutException;
50 
51 /** Tests {@link SharedPreferencesSyncManager} APIs. */
52 @RunWith(JUnit4.class)
53 public class SharedPreferencesSyncManagerUnitTest extends DeviceSupportedBaseTest {
54 
55     private static final String KEY_TO_UPDATE = "hello1";
56     private static final SharedPreferencesKey KEY_WITH_TYPE_TO_UPDATE =
57             new SharedPreferencesKey(KEY_TO_UPDATE, SharedPreferencesKey.KEY_TYPE_STRING);
58     private static final Map<String, String> TEST_DATA =
59             Map.of(KEY_TO_UPDATE, "world1", "hello2", "world2", "empty", "");
60     private static final Set<String> KEYS_TO_SYNC = Set.of(KEY_TO_UPDATE, "hello2", "empty");
61     private static final Set<SharedPreferencesKey> KEYS_WITH_TYPE_TO_SYNC =
62             Set.of(
63                     new SharedPreferencesKey(KEY_TO_UPDATE, SharedPreferencesKey.KEY_TYPE_STRING),
64                     new SharedPreferencesKey("hello2", SharedPreferencesKey.KEY_TYPE_STRING),
65                     new SharedPreferencesKey("empty", SharedPreferencesKey.KEY_TYPE_STRING));
66 
67     private static final int SANDBOX_NOT_AVAILABLE_ERROR_CODE =
68             ISharedPreferencesSyncCallback.SANDBOX_NOT_AVAILABLE;
69     private static final String SANDBOX_NOT_AVAILABLE_ERROR_MSG = "Sandbox has not started yet";
70 
71     private SharedPreferencesSyncManager mSyncManager;
72     private FakeSdkSandboxManagerService mSdkSandboxManagerService;
73     private Context mContext;
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         mContext = InstrumentationRegistry.getContext();
78         mSdkSandboxManagerService = new FakeSdkSandboxManagerService();
79         mSyncManager = new SharedPreferencesSyncManager(mContext, mSdkSandboxManagerService);
80     }
81 
82     @After
tearDown()83     public void tearDown() throws Exception {
84         getDefaultSharedPreferences().edit().clear().commit();
85     }
86 
87     @Test
test_sharedPreferencesSyncManager_isSingleton()88     public void test_sharedPreferencesSyncManager_isSingleton() throws Exception {
89         final SharedPreferencesSyncManager manager1 =
90                 SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
91         final SharedPreferencesSyncManager manager2 =
92                 SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
93         assertThat(manager1).isSameInstanceAs(manager2);
94 
95         Context mockContext = Mockito.mock(Context.class);
96         Mockito.when(mockContext.getPackageName()).thenReturn(mContext.getPackageName());
97         final SharedPreferencesSyncManager manager3 =
98                 SharedPreferencesSyncManager.getInstance(mockContext, mSdkSandboxManagerService);
99         assertThat(manager1).isSameInstanceAs(manager3);
100     }
101 
102     @Test
test_sharedPreferencesSyncManager_isSingletonPerPackage()103     public void test_sharedPreferencesSyncManager_isSingletonPerPackage() throws Exception {
104         final SharedPreferencesSyncManager manager1 =
105                 SharedPreferencesSyncManager.getInstance(mContext, mSdkSandboxManagerService);
106 
107         Context mockContext = Mockito.mock(Context.class);
108         final SharedPreferencesSyncManager manager2 =
109                 SharedPreferencesSyncManager.getInstance(mockContext, mSdkSandboxManagerService);
110         assertThat(manager1).isNotSameInstanceAs(manager2);
111     }
112 
113     @Test
test_addSyncKeys_isIncremental()114     public void test_addSyncKeys_isIncremental() throws Exception {
115         // Add one key
116         mSyncManager.addSharedPreferencesSyncKeys(Set.of("foo"));
117         assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly("foo");
118 
119         // Add another key
120         mSyncManager.addSharedPreferencesSyncKeys(Set.of("bar"));
121         assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly("foo", "bar");
122     }
123 
124     @Test
test_addSyncKeys_isIncremental_sameKeyCanBeAdded()125     public void test_addSyncKeys_isIncremental_sameKeyCanBeAdded() throws Exception {
126         mSyncManager.addSharedPreferencesSyncKeys(Set.of("foo"));
127         assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly("foo");
128 
129         mSyncManager.addSharedPreferencesSyncKeys(Set.of("foo"));
130         assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly("foo");
131     }
132 
133     @Test
test_removeKeys()134     public void test_removeKeys() throws Exception {
135         mSyncManager.addSharedPreferencesSyncKeys(Set.of("foo", "bar"));
136 
137         // Remove key
138         mSyncManager.removeSharedPreferencesSyncKeys(Set.of("foo"));
139 
140         assertThat(mSyncManager.getSharedPreferencesSyncKeys()).containsExactly("bar");
141     }
142 
143     @Test
test_removeKeys_updateSentForRemoval()144     public void test_removeKeys_updateSentForRemoval() throws Exception {
145         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
146 
147         // Remove key
148         mSyncManager.removeSharedPreferencesSyncKeys(Set.of(KEY_TO_UPDATE));
149 
150         final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
151         assertThat(update.getData().keySet()).doesNotContain(Set.of(KEY_TO_UPDATE));
152         assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
153     }
154 
155     @Test
test_bulkSync_syncSpecifiedKeys()156     public void test_bulkSync_syncSpecifiedKeys() throws Exception {
157         // Populate default shared preference with test data
158         populateDefaultSharedPreference(TEST_DATA);
159         // Add specific shared keys that we want to sync
160         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
161 
162         // Verify that sync manager passes the correct data to SdkSandboxManager
163         final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
164         assertThat(mSdkSandboxManagerService.getCallingPackageName())
165                 .isEqualTo(mContext.getPackageName());
166         assertThat(capturedData.keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
167         for (String key : TEST_DATA.keySet()) {
168             assertThat(capturedData.getString(key)).isEqualTo(TEST_DATA.get(key));
169         }
170     }
171 
172     @Test
test_bulkSync_syncMissingKeys()173     public void test_bulkSync_syncMissingKeys() throws Exception {
174         // Add specific shared keys that we want to sync
175         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
176 
177         // Verify that sync manager passes empty value for missing keys
178         final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
179         assertThat(update.getKeysInUpdate()).containsExactlyElementsIn(KEYS_WITH_TYPE_TO_SYNC);
180         assertThat(update.getData().keySet()).isEmpty();
181     }
182 
183     @Test
test_bulkSync_ignoreUnspecifiedKeys()184     public void test_bulkSync_ignoreUnspecifiedKeys() throws Exception {
185         // Populate default shared preference and set specific keys for sycing
186         populateDefaultSharedPreference(TEST_DATA);
187         // Populate extra data outside of shared key list
188         populateDefaultSharedPreference(Map.of("extraKey", "notSpecifiedByApi"));
189 
190         // Set specific shared keys that we want to sync
191         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
192 
193         // Verify that sync manager passes the correct data to SdkSandboxManager
194         final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
195         assertThat(capturedData.keySet()).containsExactlyElementsIn(TEST_DATA.keySet());
196     }
197 
198     @Test
test_bulkSync_supportsAllTypesOfValues()199     public void test_bulkSync_supportsAllTypesOfValues() throws Exception {
200         // Populate default shared preference with all valid types
201 
202         final SharedPreferences pref = getDefaultSharedPreferences();
203         final SharedPreferences.Editor editor = pref.edit();
204         editor.putString("string", "value");
205         editor.putBoolean("boolean", true);
206         editor.putFloat("float", 1.2f);
207         editor.putInt("int", 1);
208         editor.putLong("long", 1L);
209         editor.putStringSet("set", Set.of("value"));
210         editor.commit();
211 
212         // Set keys to sync and then sync data
213         final Set<String> keysToSync = Set.of("string", "boolean", "float", "int", "long", "set");
214         mSyncManager.addSharedPreferencesSyncKeys(keysToSync);
215 
216         // Verify that sync manager passes the correct data to SdkSandboxManager
217         final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
218         assertThat(capturedData.getString("string")).isEqualTo(pref.getString("string", ""));
219         assertThat(capturedData.getBoolean("boolean")).isEqualTo(pref.getBoolean("boolean", false));
220         assertThat(capturedData.getFloat("float")).isEqualTo(pref.getFloat("float", 0.0f));
221         assertThat(capturedData.getInt("int")).isEqualTo(pref.getInt("int", 0));
222         assertThat(capturedData.getLong("long")).isEqualTo(pref.getLong("long", 0L));
223         assertThat(capturedData.getStringArrayList("set"))
224                 .containsExactlyElementsIn(pref.getStringSet("set", Collections.emptySet()));
225         assertThat(capturedData.keySet()).hasSize(6);
226     }
227 
228     @Test
test_updateListener_syncsFurtherUpdates()229     public void test_updateListener_syncsFurtherUpdates() throws Exception {
230         // Set specified keys for sycing and register listener
231         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
232 
233         // Update the SharedPreference to trigger listeners
234         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
235 
236         // Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
237         mSdkSandboxManagerService.blockForReceivingUpdates(2);
238         final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
239         assertThat(capturedData.keySet()).containsExactly(KEY_TO_UPDATE);
240         assertThat(capturedData.getString(KEY_TO_UPDATE)).isEqualTo("update");
241     }
242 
243     @Test
test_updateListener_ignoresUnspecifiedKeys()244     public void test_updateListener_ignoresUnspecifiedKeys() throws Exception {
245         // Set specified keys for sycing and register listener
246         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
247         mSdkSandboxManagerService.clearUpdates();
248 
249         // Update the SharedPreference to trigger listeners
250         getDefaultSharedPreferences().edit().putString("unspecified_key", "update").commit();
251 
252         // Verify SdkSandboxManagerService does not receive the update for unspecified key
253         assertThrows(
254                 TimeoutException.class,
255                 () -> mSdkSandboxManagerService.blockForReceivingUpdates(1));
256     }
257 
258     @Test
test_updateListener_supportsAllTypesOfValues()259     public void test_updateListener_supportsAllTypesOfValues() throws Exception {
260         // Set keys to sync and then sync data to register listener
261         final Set<String> keysToSync = Set.of("string", "boolean", "float", "int", "long", "set");
262         mSyncManager.addSharedPreferencesSyncKeys(keysToSync);
263 
264         // Clear the bulk update for ease of reasoning
265         mSdkSandboxManagerService.clearUpdates();
266 
267         // Update the shared preference
268         final SharedPreferences pref = getDefaultSharedPreferences();
269         final SharedPreferences.Editor editor = pref.edit();
270         editor.putString("string", "value");
271         editor.putBoolean("boolean", true);
272         editor.putFloat("float", 1.2f);
273         editor.putInt("int", 1);
274         editor.putLong("long", 1L);
275         editor.putStringSet("set", Set.of("value"));
276         editor.commit();
277 
278         // Verify that sync manager receives one bundle for each key update
279         mSdkSandboxManagerService.blockForReceivingUpdates(6);
280         final ArrayList<SharedPreferencesUpdate> allUpdates =
281                 mSdkSandboxManagerService.getAllUpdates();
282         assertThat(allUpdates).hasSize(6);
283         for (SharedPreferencesUpdate update : allUpdates) {
284             final Bundle data = update.getData();
285             assertThat(data.keySet()).hasSize(1);
286             final String key = data.keySet().toArray()[0].toString();
287             if (key.equals("string")) {
288                 assertThat(data.getString(key)).isEqualTo(pref.getString(key, ""));
289             } else if (key.equals("boolean")) {
290                 assertThat(data.getBoolean(key)).isEqualTo(pref.getBoolean(key, false));
291             } else if (key.equals("float")) {
292                 assertThat(data.getFloat(key)).isEqualTo(pref.getFloat(key, 0.0f));
293             } else if (key.equals("int")) {
294                 assertThat(data.getInt(key)).isEqualTo(pref.getInt(key, 0));
295             } else if (key.equals("long")) {
296                 assertThat(data.getLong(key)).isEqualTo(pref.getLong(key, 0L));
297             } else if (key.equals("set")) {
298                 assertThat(data.getStringArrayList(key))
299                         .containsExactlyElementsIn(pref.getStringSet(key, Collections.emptySet()));
300             } else {
301                 fail("Unknown key found");
302             }
303         }
304     }
305 
306     /** Test that we can handle removal of keys */
307     @Test
test_updateListener_removeKey()308     public void test_updateListener_removeKey() throws Exception {
309         populateDefaultSharedPreference(TEST_DATA);
310         // Set keys to sync and then sync data to register listener
311         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
312 
313         // Update the SharedPreference to trigger listeners
314         getDefaultSharedPreferences().edit().remove(KEY_TO_UPDATE).commit();
315 
316         // Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
317         mSdkSandboxManagerService.blockForReceivingUpdates(2);
318         final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
319         assertThat(update.getData().keySet()).doesNotContain(KEY_TO_UPDATE);
320         assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
321     }
322 
323     /** Test that we can handle removal of keys by putting null */
324     @Test
test_updateListener_putNullValueForKey()325     public void test_updateListener_putNullValueForKey() throws Exception {
326         populateDefaultSharedPreference(TEST_DATA);
327         // Set keys to sync and then sync data to register listener
328         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
329 
330         // Update the SharedPreference to trigger listeners
331         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, null).commit();
332 
333         // Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
334         mSdkSandboxManagerService.blockForReceivingUpdates(2);
335         final SharedPreferencesUpdate update = mSdkSandboxManagerService.getLastUpdate();
336         assertThat(update.getData().keySet()).doesNotContain(KEY_TO_UPDATE);
337         assertThat(update.getKeysInUpdate()).containsExactly(KEY_WITH_TYPE_TO_UPDATE);
338     }
339 
340     @Test
test_updateListener_removeAllKeys()341     public void test_updateListener_removeAllKeys() throws Exception {
342         populateDefaultSharedPreference(TEST_DATA);
343         // Set keys to sync and then sync data to register listener
344         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
345 
346         // Clear all keys
347         getDefaultSharedPreferences().edit().clear().commit();
348 
349         // Verify that SyncManager tried to sync only twice: once for bulk and once for live update.
350         mSdkSandboxManagerService.blockForReceivingUpdates(2);
351         final SharedPreferencesUpdate lastUpdate = mSdkSandboxManagerService.getLastUpdate();
352         assertThat(lastUpdate.getData().keySet()).isEmpty();
353         assertThat(lastUpdate.getKeysInUpdate()).containsExactlyElementsIn(KEYS_WITH_TYPE_TO_SYNC);
354     }
355 
356     @Test
test_updateListener_multipleCalls_updateListenerRegisteredOnce()357     public void test_updateListener_multipleCalls_updateListenerRegisteredOnce() throws Exception {
358         // Add keys to sync and then sync data to register listener
359         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
360 
361         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
362 
363         // Verify updating SharedPreferences results in only one update
364         mSdkSandboxManagerService.clearUpdates(); // For cleaner observation
365         // Update the SharedPreference to trigger listeners
366         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
367         // Only one update should be received
368         assertThrows(
369                 TimeoutException.class,
370                 () -> mSdkSandboxManagerService.blockForReceivingUpdates(2));
371     }
372 
373     @Test
test_syncDataFromClient_reusesCallback()374     public void test_syncDataFromClient_reusesCallback() throws Exception {
375         // Set keys to sync and then sync data to register listener
376         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
377         mSdkSandboxManagerService.blockForReceivingUpdates(1);
378         final ISharedPreferencesSyncCallback bulkSyncCallback =
379                 mSdkSandboxManagerService.getLastCallback();
380 
381         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
382         mSdkSandboxManagerService.blockForReceivingUpdates(2);
383         final ISharedPreferencesSyncCallback updateListenerCallback1 =
384                 mSdkSandboxManagerService.getLastCallback();
385 
386         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update2").commit();
387         mSdkSandboxManagerService.blockForReceivingUpdates(3);
388         final ISharedPreferencesSyncCallback updateListenerCallback2 =
389                 mSdkSandboxManagerService.getLastCallback();
390 
391         assertThat(bulkSyncCallback).isSameInstanceAs(updateListenerCallback1);
392         assertThat(bulkSyncCallback).isSameInstanceAs(updateListenerCallback2);
393     }
394 
395     /** Test that we support starting sync before sandbox is created */
396     @Test
test_onError_bulksync_SandboxNotAvailableError()397     public void test_onError_bulksync_SandboxNotAvailableError() throws Exception {
398         // Set keys to sync and then sync data to register listener
399         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
400 
401         // Report sandbox has not been created
402         mSdkSandboxManagerService
403                 .getLastCallback()
404                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
405         // Verify that sync was still running
406         assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
407     }
408 
409     /** Test that we support starting sync before sandbox is created */
410     @Test
test_onError_updateListener_sandboxNotAvailableError()411     public void test_onError_updateListener_sandboxNotAvailableError() throws Exception {
412         // Set keys to sync and then sync data to register listener
413         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
414 
415         // Update the SharedPreference to trigger listeners
416         mSdkSandboxManagerService.clearUpdates(); // For ease of reasoning
417         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
418 
419         // Wait until update is received
420         mSdkSandboxManagerService.blockForReceivingUpdates(1);
421         // Report an error via the callback
422         mSdkSandboxManagerService
423                 .getLastCallback()
424                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
425         // Verify that sync is in waiting state now
426         assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
427     }
428 
429     @Test
test_onError_updateListener_notRegisteredWhenWaitingForSandbox()430     public void test_onError_updateListener_notRegisteredWhenWaitingForSandbox() throws Exception {
431         // Set keys to sync and then sync data to register listener
432         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
433 
434         // Send SyncManager to waiting state
435         mSdkSandboxManagerService
436                 .getLastCallback()
437                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
438 
439         // Update the SharedPreference to trigger listeners
440         mSdkSandboxManagerService.clearUpdates(); // For ease of reasoning
441         getDefaultSharedPreferences().edit().putString(KEY_TO_UPDATE, "update").commit();
442 
443         // Verify update not received
444         assertThrows(
445                 TimeoutException.class,
446                 () -> mSdkSandboxManagerService.blockForReceivingUpdates(1));
447     }
448 
449     @Test
test_onSandboxStart_bulkSyncRetries()450     public void test_onSandboxStart_bulkSyncRetries() throws Exception {
451         // Set keys to sync and then sync data to register listener
452         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
453 
454         // Send SyncManager to waiting state
455         mSdkSandboxManagerService
456                 .getLastCallback()
457                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
458 
459         // Notify syncmanager eventually when sandbox starts
460         final ISharedPreferencesSyncCallback firstCallback =
461                 mSdkSandboxManagerService.getLastCallback();
462         mSdkSandboxManagerService.getLastCallback().onSandboxStart();
463 
464         // Verify another bulk sync update is sent to SdkSandboxManagerService
465         mSdkSandboxManagerService.blockForReceivingUpdates(2);
466 
467         // Notify again, but this time it should not trigger a new update since we were not waiting.
468         mSdkSandboxManagerService.getLastCallback().onSandboxStart();
469         firstCallback.onSandboxStart();
470         assertThrows(
471                 TimeoutException.class,
472                 () -> mSdkSandboxManagerService.blockForReceivingUpdates(3));
473     }
474 
475     /** Write all key-values provided in the map to app's default SharedPreferences */
populateDefaultSharedPreference(Map<String, String> data)476     private void populateDefaultSharedPreference(Map<String, String> data) {
477         final SharedPreferences.Editor editor = getDefaultSharedPreferences().edit();
478         for (Map.Entry<String, String> entry : data.entrySet()) {
479             editor.putString(entry.getKey(), entry.getValue());
480         }
481         editor.apply();
482     }
483 
getDefaultSharedPreferences()484     private SharedPreferences getDefaultSharedPreferences() {
485         final Context appContext = mContext.getApplicationContext();
486         return PreferenceManager.getDefaultSharedPreferences(appContext);
487     }
488 
489     private static class FakeSdkSandboxManagerService extends StubSdkSandboxManagerService {
490         @GuardedBy("this")
491         private ArrayList<SharedPreferencesUpdate> mUpdateCache = new ArrayList<>();
492 
493         @GuardedBy("this")
494         private ISharedPreferencesSyncCallback mLastCallback = null;
495 
496         @GuardedBy("this")
497         private String mCallingPackageName = null;
498 
499         /** Gets updated when {@link blockForReceivingUpdates} is called. */
500         private CountDownLatch mWaitForMoreUpdates = new CountDownLatch(0);
501 
502         @Override
syncDataFromClient( String callingPackageName, SandboxLatencyInfo sandboxLatencyInfo, SharedPreferencesUpdate update, ISharedPreferencesSyncCallback callback)503         public synchronized void syncDataFromClient(
504                 String callingPackageName,
505                 SandboxLatencyInfo sandboxLatencyInfo,
506                 SharedPreferencesUpdate update,
507                 ISharedPreferencesSyncCallback callback) {
508             if (mCallingPackageName == null) {
509                 mCallingPackageName = callingPackageName;
510             } else {
511                 assertThat(mCallingPackageName).isEqualTo(callingPackageName);
512             }
513 
514             mUpdateCache.add(update);
515             mLastCallback = callback;
516             mWaitForMoreUpdates.countDown();
517         }
518 
getCallingPackageName()519         public synchronized String getCallingPackageName() {
520             return mCallingPackageName;
521         }
522 
523         @Nullable
getLastUpdate()524         public synchronized SharedPreferencesUpdate getLastUpdate() {
525             if (mUpdateCache.isEmpty()) {
526                 throw new AssertionError(
527                         "Fake SdkSandboxManagerService did not receive any update");
528             }
529             return mUpdateCache.get(mUpdateCache.size() - 1);
530         }
531 
532         @Nullable
getLastCallback()533         public synchronized ISharedPreferencesSyncCallback getLastCallback() {
534             return mLastCallback;
535         }
536 
getAllUpdates()537         public synchronized ArrayList<SharedPreferencesUpdate> getAllUpdates() {
538             return new ArrayList<>(mUpdateCache);
539         }
540 
getNumberOfUpdatesReceived()541         public synchronized int getNumberOfUpdatesReceived() {
542             return mUpdateCache.size();
543         }
544 
clearUpdates()545         public synchronized void clearUpdates() {
546             mUpdateCache.clear();
547         }
548 
blockForReceivingUpdates(int numberOfUpdates)549         public void blockForReceivingUpdates(int numberOfUpdates) throws Exception {
550             synchronized (this) {
551                 final int updatesNeeded = numberOfUpdates - getNumberOfUpdatesReceived();
552                 if (updatesNeeded <= 0) {
553                     return;
554                 }
555                 mWaitForMoreUpdates = new CountDownLatch(updatesNeeded);
556             }
557             if (!mWaitForMoreUpdates.await(5000, TimeUnit.MILLISECONDS)) {
558                 throw new TimeoutException(
559                         "Failed to receive required number of updates. Required: "
560                                 + numberOfUpdates
561                                 + ", but found: "
562                                 + getNumberOfUpdatesReceived());
563             }
564         }
565     }
566 }
567