[camerax] Fixes relistening to `onStreamedFrameAvailable`'s stream behavior (#4511)

Removes incorrect assumption causing image stream to stop emitting data after subscription to stream is canceled and then the stream is listened to again.

Fixes https://github.com/flutter/flutter/issues/130005.
diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 01fb140..ab60e8a 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.0+11
+
+* Fixes issue with image data not being emitted after relistening to stream returned by `onStreamedFrameAvailable`.
+
 ## 0.5.0+10
 
 * Implements off, auto, and always flash mode configurations for image capture.
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index 99f179b..9a58e6f 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -605,12 +605,6 @@
   /// Configures the [imageAnalysis] instance for image streaming and binds it
   /// to camera lifecycle controlled by the [processCameraProvider].
   Future<void> _configureAndBindImageAnalysisToLifecycle() async {
-    if (imageAnalysis != null &&
-        await processCameraProvider!.isBound(imageAnalysis!)) {
-      // imageAnalysis already configured and bound to lifecycle.
-      return;
-    }
-
     // Create Analyzer that can read image data for image streaming.
     final WeakReference<AndroidCameraCameraX> weakThis =
         WeakReference<AndroidCameraCameraX>(this);
@@ -648,9 +642,14 @@
 
     // TODO(camsim99): Support resolution configuration.
     // Defaults to YUV_420_888 image format.
-    imageAnalysis = createImageAnalysis(null);
+    imageAnalysis ??= createImageAnalysis(null);
     unawaited(imageAnalysis!.setAnalyzer(analyzer));
 
+    if (await processCameraProvider!.isBound(imageAnalysis!)) {
+      // No need to bind imageAnalysis to lifecycle again.
+      return;
+    }
+
     // TODO(camsim99): Reset live camera state observers here when
     // https://github.com/flutter/packages/pull/3419 lands.
     camera = await processCameraProvider!
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 328ae28..0fea9fc 100644
--- a/packages/camera/camera_android_camerax/pubspec.yaml
+++ b/packages/camera/camera_android_camerax/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the camera plugin using the CameraX library.
 repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.5.0+10
+version: 0.5.0+11
 
 environment:
   sdk: ">=2.19.0 <4.0.0"
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
index b1c57e2..bdebdce 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
@@ -940,7 +940,47 @@
   });
 
   test(
-      'onStreamedFrameAvaiable returns stream that responds expectedly to being listened to',
+      'onStreamedFrameAvailable emits CameraImageData when listened to after cancelation',
+      () async {
+    final FakeAndroidCameraCameraX camera =
+        FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
+    final MockProcessCameraProvider mockProcessCameraProvider =
+        MockProcessCameraProvider();
+    final MockCamera mockCamera = MockCamera();
+    const int cameraId = 22;
+
+    camera.processCameraProvider = mockProcessCameraProvider;
+    camera.cameraSelector = MockCameraSelector();
+
+    when(mockProcessCameraProvider.bindToLifecycle(any, any))
+        .thenAnswer((_) => Future<Camera>.value(mockCamera));
+    when(mockCamera.getCameraInfo())
+        .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
+
+    final CameraImageData mockCameraImageData = MockCameraImageData();
+    final Stream<CameraImageData> imageStream =
+        camera.onStreamedFrameAvailable(cameraId);
+
+    // Listen to image stream.
+    final StreamSubscription<CameraImageData> imageStreamSubscription =
+        imageStream.listen((CameraImageData data) {});
+
+    // Cancel subscription to image stream.
+    await imageStreamSubscription.cancel();
+    final Stream<CameraImageData> imageStream2 =
+        camera.onStreamedFrameAvailable(cameraId);
+
+    // Listen to image stream again.
+    final StreamQueue<CameraImageData> streamQueue =
+        StreamQueue<CameraImageData>(imageStream2);
+    camera.cameraImageDataStreamController!.add(mockCameraImageData);
+
+    expect(await streamQueue.next, equals(mockCameraImageData));
+    await streamQueue.cancel();
+  });
+
+  test(
+      'onStreamedFrameAvailable returns stream that responds expectedly to being listened to',
       () async {
     final FakeAndroidCameraCameraX camera =
         FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
@@ -963,6 +1003,8 @@
     camera.processCameraProvider = mockProcessCameraProvider;
     camera.cameraSelector = mockCameraSelector;
 
+    when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
+        .thenAnswer((_) async => Future<bool>.value(false));
     when(mockProcessCameraProvider.bindToLifecycle(
             mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
         .thenAnswer((_) async => mockCamera);
@@ -989,7 +1031,9 @@
     final Analyzer capturedAnalyzer =
         verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
             as Analyzer;
-    verify(mockProcessCameraProvider.bindToLifecycle(
+    await untilCalled(
+        mockProcessCameraProvider.isBound(camera.mockImageAnalysis));
+    await untilCalled(mockProcessCameraProvider.bindToLifecycle(
         mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
 
     await capturedAnalyzer.analyze(mockImageProxy);
@@ -1011,7 +1055,7 @@
   });
 
   test(
-      'onStreamedFrameAvaiable returns stream that responds expectedly to being canceled',
+      'onStreamedFrameAvailable returns stream that responds expectedly to being canceled',
       () async {
     final FakeAndroidCameraCameraX camera =
         FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);