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