Move UI logic to UI thread

All UI updates should happen on the UI thread. Currently, the various
callbacks in PreviewActivity are spread across different threads.
This leads to unhandled race condition when attempting to access the
PreviewActivity state.

This CL moves all activity logic to the UI thread. All non-UI logic is
already offloaded to a separate thread, so moving all UI logic to the
UI thread should provide a consistent view of the UI state.

Bug: 291133993
Test: Manually played around with PreviewActivity without any crashes.

Merged-In: I43c21206a3b815dd486646818ba569e1d0ec0080
Change-Id: I43c21206a3b815dd486646818ba569e1d0ec0080
(cherry picked from commit 68e16d807f10a5fb032be1aee26fd6e626970688)
Signed-off-by: Jayant Chowdhary <jchowdhary@google.com>
diff --git a/src/com/android/DeviceAsWebcam/DeviceAsWebcamPreview.java b/src/com/android/DeviceAsWebcam/DeviceAsWebcamPreview.java
index c2676f8..c10f553 100644
--- a/src/com/android/DeviceAsWebcam/DeviceAsWebcamPreview.java
+++ b/src/com/android/DeviceAsWebcam/DeviceAsWebcamPreview.java
@@ -79,27 +79,30 @@
                 @Override
                 public void onSurfaceTextureAvailable(SurfaceTexture texture, int width,
                         int height) {
-                    if (VERBOSE) {
-                        Log.v(TAG, "onSurfaceTextureAvailable " + width + " x " + height);
-                    }
-                    mServiceReady.block();
-
-                    if (!mTextureViewSetup) {
-                        setupTextureViewLayout();
-                    }
-                    if (mLocalFgService != null) {
-                        mLocalFgService.setOnDestroyedCallback(() -> onServiceDestroyed());
-                        mLocalFgService.setPreviewSurfaceTexture(texture);
-                        if (mLocalFgService.canToggleCamera()) {
-                            mToggleCameraButton.setVisibility(View.VISIBLE);
-                            mToggleCameraButton.setOnClickListener(v -> toggleCamera());
-                        } else {
-                            mToggleCameraButton.setVisibility(View.GONE);
+                    runOnUiThread(() -> {
+                        if (VERBOSE) {
+                            Log.v(TAG, "onSurfaceTextureAvailable " + width + " x " + height);
                         }
-                        rotateUiByRotationDegrees(mLocalFgService.getCurrentRotation());
-                        mLocalFgService.setRotationUpdateListener(
-                                rotation -> rotateUiByRotationDegrees(rotation));
-                    }
+                        mServiceReady.block();
+
+                        if (!mTextureViewSetup) {
+                            setupTextureViewLayout();
+                        }
+                        if (mLocalFgService != null) {
+                            mLocalFgService.setOnDestroyedCallback(() -> onServiceDestroyed());
+                            mLocalFgService.setPreviewSurfaceTexture(texture);
+                            if (mLocalFgService.canToggleCamera()) {
+                                mToggleCameraButton.setVisibility(View.VISIBLE);
+                                mToggleCameraButton.setOnClickListener(v -> toggleCamera());
+                            } else {
+                                mToggleCameraButton.setVisibility(View.GONE);
+                            }
+                            rotateUiByRotationDegrees(mLocalFgService.getCurrentRotation());
+                            mLocalFgService.setRotationUpdateListener(
+                                    rotation -> runOnUiThread(
+                                            () -> rotateUiByRotationDegrees(rotation)));
+                        }
+                    });
                 }
 
                 @Override
@@ -112,9 +115,11 @@
 
                 @Override
                 public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
-                    if (mLocalFgService != null) {
-                        mLocalFgService.removePreviewSurfaceTexture();
-                    }
+                    runOnUiThread(() -> {
+                        if (mLocalFgService != null) {
+                            mLocalFgService.removePreviewSurfaceTexture();
+                        }
+                    });
                     return true;
                 }
 
@@ -135,8 +140,12 @@
 
         @Override
         public void onServiceDisconnected(ComponentName className) {
-            mLocalFgService = null;
-            runOnUiThread(() -> finish());
+            // Serialize updating mLocalFgService on UI Thread as all consumers of mLocalFgService
+            // run on the UI Thread.
+            runOnUiThread(() -> {
+                mLocalFgService = null;
+                finish();
+            });
         }
     };
 
@@ -211,6 +220,10 @@
     private void rotateUiByRotationDegrees(int rotation) {
         // Rotates the UI control container according to the device sensor rotation degrees and the
         // camera sensor orientation.
+        if (mLocalFgService == null) {
+            // Don't do anything if no foreground service is connected
+            return;
+        }
         int sensorOrientation = mLocalFgService.getCameraInfo().getSensorOrientation();
         if (mLocalFgService.getCameraInfo().getLensFacing()
                 == CameraCharacteristics.LENS_FACING_BACK) {
@@ -316,7 +329,8 @@
             if (mLocalFgService != null) {
                 mLocalFgService.setPreviewSurfaceTexture(mTextureView.getSurfaceTexture());
                 rotateUiByRotationDegrees(mLocalFgService.getCurrentRotation());
-                mLocalFgService.setRotationUpdateListener(this::rotateUiByRotationDegrees);
+                mLocalFgService.setRotationUpdateListener(rotation ->
+                        runOnUiThread(() -> rotateUiByRotationDegrees(rotation)));
                 updateZoomText(mLocalFgService.getZoomRatio());
             }
         } else {