Bugfix: Allow PendingValue to be set again.

In CameraController, we store the value of CameraControl if the camera is not yet initialized. e.g. zoom ratio. Then, we propagate the value after the  initialization. However we made an incorrect assumption that the camera is only initialized once. So we throw Exception if the future is completed.

In fact, the camera can be intialized twice when the app calls unbind. The following code throws an unexpected Exception:

```
cameraController.setZoomRatio(1)
cameraController.unbind()
cameraController.setZoomRatio(1)
```

The fix in this CL removes the check for Future completion and make sure that each value is only propagated once.

Bug: 294000691
Test: manual test and ./gradlew bOS
Change-Id: Ib5773bb22f89021694ba96c7b49536c7ec97bcf0
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PendingValue.java b/camera/camera-view/src/main/java/androidx/camera/view/PendingValue.java
index 5b5d1fd..1b12c9e 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PendingValue.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PendingValue.java
@@ -17,7 +17,6 @@
 package androidx.camera.view;
 
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
-import static androidx.core.util.Preconditions.checkState;
 
 import static java.util.Objects.requireNonNull;
 
@@ -31,6 +30,7 @@
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Pair;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -46,11 +46,7 @@
 class PendingValue<T> {
 
     @Nullable
-    private T mValue;
-    @Nullable
-    private ListenableFuture<Void> mListenableFuture;
-    @Nullable
-    private CallbackToFutureAdapter.Completer<Void> mCompleter;
+    private Pair<CallbackToFutureAdapter.Completer<Void>, T> mCompleterAndValue;
 
     /**
      * Assigns the pending value.
@@ -60,19 +56,14 @@
     @MainThread
     ListenableFuture<Void> setValue(@NonNull T value) {
         checkMainThread();
-        if (mListenableFuture != null) {
-            checkState(!mListenableFuture.isDone(),
-                    "#setValue() is called after the value is propagated.");
-            // Cancel the previous ListenableFuture.
-            mListenableFuture.cancel(false);
-        }
-        // Track the pending value and the ListenableFuture.
-        mValue = value;
-        mListenableFuture = CallbackToFutureAdapter.getFuture(completer -> {
-            mCompleter = completer;
+        return CallbackToFutureAdapter.getFuture(completer -> {
+            // Track the pending value and the completer.
+            if (mCompleterAndValue != null) {
+                requireNonNull(mCompleterAndValue.first).setCancelled();
+            }
+            mCompleterAndValue = new Pair<>(completer, value);
             return "PendingValue " + value;
         });
-        return mListenableFuture;
     }
 
     /**
@@ -83,8 +74,10 @@
     @MainThread
     void propagateIfHasValue(Function<T, ListenableFuture<Void>> setValueFunction) {
         checkMainThread();
-        if (mValue != null) {
-            Futures.propagate(setValueFunction.apply(mValue), requireNonNull(mCompleter));
+        if (mCompleterAndValue != null) {
+            Futures.propagate(setValueFunction.apply(mCompleterAndValue.second),
+                    requireNonNull(mCompleterAndValue.first));
+            mCompleterAndValue = null;
         }
     }
 }
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
index 2474292..9033f3e 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
@@ -152,6 +152,26 @@
     }
 
     @Test
+    fun unbindController_canSetPendingValueAgain() {
+        // Arrange: set pending values
+        var linearZoomFuture = controller.setLinearZoom(LINEAR_ZOOM)
+
+        // Act: complete initialization.
+        completeCameraInitialization()
+        // Assert: pending value is set.
+        assertThat(fakeCameraControl.linearZoom).isEqualTo(LINEAR_ZOOM)
+        assertThat(linearZoomFuture.isDone).isTrue()
+
+        // Act: unbind controller, set pending value again and rebind.
+        controller.unbind()
+        linearZoomFuture = controller.setLinearZoom(1F)
+        controller.bindToLifecycle(FakeLifecycleOwner())
+        // Assert: pending value is set to new value.
+        assertThat(fakeCameraControl.linearZoom).isEqualTo(1F)
+        assertThat(linearZoomFuture.isDone).isTrue()
+    }
+
+    @Test
     fun initCompletes_torchStatePropagated() {
         // Arrange: get LiveData before init completes
         val torchState = controller.torchState
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PendingValueTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/PendingValueTest.kt
index 91263d6..1beccf2 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/PendingValueTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/PendingValueTest.kt
@@ -34,6 +34,59 @@
 class PendingValueTest {
 
     @Test
+    fun assignPendingValueAfterPropagation_valueAssigned() {
+        // Arrange: create a pending value.
+        val pendingValue = PendingValue<Boolean>()
+        var assignedValue: Boolean? = null
+
+        // Act: set the pending value and propagate the result.
+        val future1 = pendingValue.setValue(true)
+        pendingValue.propagateIfHasValue {
+            assignedValue = it
+            Futures.immediateFuture(null)
+        }
+        // Assert: value is propagated to core. App future is done.
+        assertThat(assignedValue).isTrue()
+        assertThat(future1.isDone).isTrue()
+
+        // Act: set the pending value again.
+        val future2 = pendingValue.setValue(false)
+        pendingValue.propagateIfHasValue {
+            assignedValue = it
+            Futures.immediateFuture(null)
+        }
+        // Assert: value is propagated to core. App future is done.
+        assertThat(assignedValue).isFalse()
+        assertThat(future2.isDone).isTrue()
+    }
+
+    @Test
+    fun propagationTwiceForTheSameAssignment_theSecondTimeIsNoOp() {
+        // Arrange: create a pending value.
+        val pendingValue = PendingValue<Boolean>()
+
+        // Act: set the pending value and propagate the result.
+        val future1 = pendingValue.setValue(true)
+        var assignedValue: Boolean? = null
+        pendingValue.propagateIfHasValue {
+            assignedValue = it
+            Futures.immediateFuture(null)
+        }
+        // Assert: value is propagated to core. App future is done.
+        assertThat(assignedValue).isTrue()
+        assertThat(future1.isDone).isTrue()
+
+        // Act: propagate the result again. e.g. when camera is restarted
+        var assignedValue2: Boolean? = null
+        pendingValue.propagateIfHasValue {
+            assignedValue2 = it
+            Futures.immediateFuture(null)
+        }
+        // Assert: the value set in the previous session will not be propagated.
+        assertThat(assignedValue2).isNull()
+    }
+
+    @Test
     fun assignPendingValueTwice_theSecondValueIsAssigned() {
         // Arrange.
         val pendingValue = PendingValue<Boolean>()