Remove concept of "stop" state from SharedPreferencesSyncManager

We have removed stop() api and removed callbacks for notification of
error. The idea is that SyncManager will deal with all errors internally
without putting any burden to the user.

There can be two kind of errors: sandbox is unavailable and everything
else.

If sandbox is unavailable, we should move to a waiting state and resume
syncing once it is available.

For all kinds of other errors, log the error and just keep on syncing.
Ideally, there should be no errors at all. If there are any, we have to
fix them to gurantee seamless syncing.

If user wants to stop syncing, they can just remove all the keys from
sync pool. API for removal of keys will be added soon.

So there is only two state for SyncManager now: running and waiting.
That can be figured by a single boolean, so refactored accordingly.

Bug: 239403323
Test: atest SdkSdkSandboxFrameworkUnitTests
Change-Id: Ibe0b75b95c38125711bc5726dee30b6693ad06c4
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java b/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
index f0e914a..71e22a5 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SharedPreferencesSyncManager.java
@@ -54,10 +54,6 @@
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private boolean mIsRunning = false;
-
-    // Set to true if initial bulk sync fails due to sandbox being unavailable
-    @GuardedBy("mLock")
     private boolean mWaitingForSandbox = false;
 
     // Set to a listener after initial bulk sync is successful
@@ -109,7 +105,6 @@
             @NonNull Set<SharedPreferencesKey> keysWithTypeToSync) {
         // TODO(b/239403323): Validate the parameters in SdkSandboxManager
         synchronized (mLock) {
-            mIsRunning = true;
             for (SharedPreferencesKey keyWithType : keysWithTypeToSync) {
                 mKeysToSync.put(keyWithType.getName(), keyWithType);
             }
@@ -128,22 +123,19 @@
         }
     }
 
-    // TODO(b/239403323): In incremental api, sync will always be running
-    /** Returns true if sync is running currently. */
-    public boolean isSyncRunning() {
+    /**
+     * Returns true if sync is in waiting state.
+     *
+     * <p>Sync transitions into waiting state whenever sdksandbox is unavailable. It resumes syncing
+     * again when SdkSandboxManager notifies us that sdksandbox is available again.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isWaitingForSandbox() {
         synchronized (mLock) {
-            return mIsRunning;
+            return mWaitingForSandbox;
         }
     }
 
-    @GuardedBy("mLock")
-    private void cleanUp() {
-        getDefaultSharedPreferences().unregisterOnSharedPreferenceChangeListener(mListener);
-        mListener = null;
-        mIsRunning = false;
-        mWaitingForSandbox = false;
-    }
-
     /**
      * Syncs data to SdkSandbox.
      *
@@ -161,9 +153,6 @@
             }
 
             bulkSyncData();
-
-            // Register listener for syncing future updates
-            getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(mListener);
         }
     }
 
@@ -208,7 +197,7 @@
 
     private void handleSuccess() {
         synchronized (mLock) {
-            if (mIsRunning && !mWaitingForSandbox && mListener == null) {
+            if (!mWaitingForSandbox && mListener == null) {
                 mListener = new ChangeListener();
                 getDefaultSharedPreferences().registerOnSharedPreferenceChangeListener(mListener);
             }
@@ -227,20 +216,13 @@
 
     private void handleError(int errorCode, String errorMsg) {
         synchronized (mLock) {
-            if (!mIsRunning) {
-                return;
-            }
-
-            // If we are not waiting for sandbox and we haven't registered a listener yet, then
-            // this error is from initial bulk sync request.
+            // Transition to waiting state when sandbox is unavailable
             if (!mWaitingForSandbox
-                    && mListener == null
                     && errorCode == ISharedPreferencesSyncCallback.SANDBOX_NOT_AVAILABLE) {
                 // Wait for sandbox to start. When it starts, server will call onSandboxStart
                 mWaitingForSandbox = true;
                 return;
             }
-            cleanUp();
         }
     }
 
@@ -254,8 +236,8 @@
         public void onSharedPreferenceChanged(SharedPreferences pref, @Nullable String key) {
             // Sync specified keys only
             synchronized (mLock) {
-                // Do not sync if sync has been stopped
-                if (!mIsRunning) {
+                // Do not sync if we are in waiting state
+                if (mWaitingForSandbox) {
                     return;
                 }
 
@@ -264,6 +246,7 @@
                     bulkSyncData();
                     return;
                 }
+
                 if (!mKeysToSync.containsKey(key)) {
                     return;
                 }
diff --git a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
index 654b226..2f9d566 100644
--- a/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
+++ b/sdksandbox/tests/unittest/src/android/app/sdksandbox/SharedPreferencesSyncManagerUnitTest.java
@@ -34,6 +34,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -390,7 +391,7 @@
         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
     }
 
-    // TODO(b/239403323): On error, the sync should keep on running
+    @Ignore("b/239403323: Get rid of internal errors, since we are not notifying user about it")
     @Test
     public void test_onError_bulksync_stopsOnInternalError() throws Exception {
         // Set keys to sync and then sync data to register listener
@@ -401,13 +402,16 @@
                 .getLastCallback()
                 .onError(INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG);
 
-        // Verify that sync is no longer running
-        assertThat(mSyncManager.isSyncRunning()).isFalse();
+        // Verify that sync is still running
+        assertThatSyncIsRunning();
     }
 
+    // TODO(b/239403323): Verify behavior when in waiting state
+
     /** Test that we support starting sync before sandbox is created */
+    @Ignore("b/239403323: Make update listener registration independent of bulk sync")
     @Test
-    public void test_onError_bulksync_doesNotStopOnSandboxNotAvailableError() throws Exception {
+    public void test_onError_bulksync_SandboxNotAvailableError() throws Exception {
         // Set keys to sync and then sync data to register listener
         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
 
@@ -416,7 +420,8 @@
                 .getLastCallback()
                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
         // Verify that sync was still running
-        assertThat(mSyncManager.isSyncRunning()).isTrue();
+        assertThatSyncIsRunning();
+        assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
     }
 
     @Test
@@ -440,7 +445,6 @@
         mSyncManager.addSharedPreferencesSyncKeys(KEYS_TO_SYNC);
     }
 
-    // TODO(b/239403323): Instead of stopping, we should switch to waiting for sandbox
     /** Test that we support starting sync before sandbox is created */
     @Test
     public void test_onError_updateListener_stopsOnSandboxNotAvailableError() throws Exception {
@@ -458,8 +462,8 @@
         mSdkSandboxManagerService
                 .getLastCallback()
                 .onError(SANDBOX_NOT_AVAILABLE_ERROR_CODE, SANDBOX_NOT_AVAILABLE_ERROR_MSG);
-        // Verify that sync was stopped
-        assertThat(mSyncManager.isSyncRunning()).isFalse();
+        // Verify that sync is in waiting state now
+        assertThat(mSyncManager.isWaitingForSandbox()).isTrue();
     }
 
     @Test
@@ -523,6 +527,27 @@
         return PreferenceManager.getDefaultSharedPreferences(appContext);
     }
 
+    private void assertThatSyncIsRunning() throws Exception {
+        // There must be some keys in the pool for sync to be running
+        final Set<SharedPreferencesKey> syncKeys = mSyncManager.getSharedPreferencesSyncKeys();
+        assertThat(syncKeys).isNotEmpty();
+
+        // If sync is still active, updating any key would result in syncing date
+        mSdkSandboxManagerService.clearUpdates(); // For easier assert
+        final SharedPreferencesKey syncKey = syncKeys.iterator().next();
+        assertThat(syncKey.getType()).isEqualTo(SharedPreferencesKey.KEY_TYPE_STRING);
+        getDefaultSharedPreferences()
+                .edit()
+                .putString(syncKey.getName(), "assertSyncRunning")
+                .commit();
+
+        // Verify that SyncManager tried to sync
+        mSdkSandboxManagerService.blockForReceivingUpdates(1);
+        final Bundle capturedData = mSdkSandboxManagerService.getLastUpdate().getData();
+        assertThat(capturedData.keySet()).containsExactly(syncKey);
+        assertThat(capturedData.getString(KEY_TO_UPDATE)).isEqualTo("assertSyncIsRunning");
+    }
+
     private static class FakeSdkSandboxManagerService extends StubSdkSandboxManagerService {
         @GuardedBy("this")
         private ArrayList<SharedPreferencesUpdate> mUpdateCache = new ArrayList<>();