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