Ensure that TaskView callbacks are made on the ui thread for the view

- In cases where the TaskView are not created on the shell main thread,
  the callbacks on the view may be made on the wrong thread leading to
  an error or the a race between usage/cleanup of the surfaces used by
  the surface view.

Fixes: 292241747
Fixes: 300870297

Test: atest TaskViewTest
Test: atest PanelTaskViewControllerTest
Test: atest ControlsUiControllerImplTest
Change-Id: I43bbd9d8089d0ab6e6bd10dd5cb4072f0029d8dc
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 0d77a2e..ef8393c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -29,12 +29,16 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -74,6 +78,7 @@
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
     private Insets mCaptionInsets;
+    private Handler mHandler;
 
     public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -81,6 +86,7 @@
         // TODO(b/266736992): Think about a better way to set the TaskViewBase on the
         //  TaskViewTaskController and vice-versa
         mTaskViewTaskController.setTaskViewBase(this);
+        mHandler = Handler.getMain();
         getHolder().addCallback(this);
     }
 
@@ -117,14 +123,16 @@
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
         onLocationChanged();
         if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+            final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
         }
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
         if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+            final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
         }
     }
 
@@ -143,7 +151,7 @@
 
     @Override
     public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
-        setResizeBackgroundColor(t, bgColor);
+        runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
     }
 
     /**
@@ -272,12 +280,14 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+        mHandler = getHandler();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        mHandler = Handler.getMain();
     }
 
     /** Returns the task info for the task in the TaskView. */
@@ -285,4 +295,24 @@
     public ActivityManager.RunningTaskInfo getTaskInfo() {
         return mTaskViewTaskController.getTaskInfo();
     }
+
+    /**
+     * Sets the handler, only for testing.
+     */
+    @VisibleForTesting
+    void setHandler(Handler viewHandler) {
+        mHandler = viewHandler;
+    }
+
+    /**
+     * Ensures that the given runnable runs on the view's thread.
+     */
+    private void runOnViewThread(Runnable r) {
+        if (mHandler.getLooper().isCurrentThread()) {
+            r.run();
+        } else {
+            // If this call is not from the same thread as the view, then post it
+            mHandler.post(r);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0088051..4afb29e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -43,6 +43,8 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
@@ -88,6 +90,10 @@
     SyncTransactionQueue mSyncQueue;
     @Mock
     Transitions mTransitions;
+    @Mock
+    Looper mViewLooper;
+    @Mock
+    Handler mViewHandler;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -105,6 +111,8 @@
                 .build();
 
         mContext = getContext();
+        doReturn(true).when(mViewLooper).isCurrentThread();
+        doReturn(mViewLooper).when(mViewHandler).getLooper();
 
         mTaskInfo = new ActivityManager.RunningTaskInfo();
         mTaskInfo.token = mToken;
@@ -132,6 +140,7 @@
         mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
                 mTaskViewTransitions, mSyncQueue));
         mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView.setHandler(mViewHandler);
         mTaskView.setListener(mExecutor, mViewListener);
     }
 
@@ -646,4 +655,17 @@
 
         assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
     }
+
+    @Test
+    public void testOnTaskInfoChangedOnSameUiThread() {
+        mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+        verify(mViewHandler, never()).post(any());
+    }
+
+    @Test
+    public void testOnTaskInfoChangedOnDifferentUiThread() {
+        doReturn(false).when(mViewLooper).isCurrentThread();
+        mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+        verify(mViewHandler).post(any());
+    }
 }