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