Keep task hidden until task appeared

- If the task is previously not visible or has no visible children at
  the point when we start controlling it in the task org, hide the task
  until we send taskAppeared to ensure that the task org can reparent
  and show it otherwise we could see a flash of the task.

  This happens mainly from two cases:
  - when starting a new task with a given win mode, we show it and wait
    for first draw before notifying the task org
  - when transitioning into pip from swipe up, the activity is hidden
    and when it requests to enter pip is made visible again

  Since we are hiding the task w/ the pending transaction, we also need
  to defer all task org callbacks until that's applied to ensure proper
  lifecycle of the calls.
- Also skip app transitions for task org tasks for now

Bug: 152809695
Bug: 152134460
Test: Open a bubble, ensure that we don't see the task in fullscreen
      first.  Enter pip, ensure that we don't see flash of the task
      before SysUI can fade it in.
Test: atest PipAnimationControllerTest
Test: atest TaskOrganizerTests
Test: atest SplitScreenTests

Change-Id: If51e98cd007faef35e99acd31b27b20eebbea010
diff --git a/core/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
index 2091c93..39a0101 100644
--- a/core/java/android/window/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -254,7 +254,9 @@
             mTaskToken = taskInfo.token;
             mTaskLeash = mTaskToken.getLeash();
             mTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .show(mSurfaceControl).apply();
+                    .show(mTaskLeash)
+                    .show(mSurfaceControl)
+                    .apply();
             if (mPendingNotifyBoundsChanged) {
                 // TODO: Either defer show or hide and synchronize show with the resize
                 notifyBoundsChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index d219a9e..dba4343 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -319,6 +319,7 @@
                     getSurfaceTransactionHelper()
                             .crop(tx, leash, getDestinationBounds())
                             .round(tx, leash, shouldApplyCornerRadius());
+                    tx.show(leash);
                     tx.apply();
                 }
             };
@@ -359,6 +360,7 @@
                     getSurfaceTransactionHelper()
                             .alpha(tx, leash, 1f)
                             .round(tx, leash, shouldApplyCornerRadius());
+                    tx.show(leash);
                     tx.apply();
                 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index 0d66340..56a7484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -51,7 +51,6 @@
 
     private PipAnimationController mPipAnimationController;
 
-    @Mock
     private SurfaceControl mLeash;
 
     @Mock
@@ -61,6 +60,10 @@
     public void setUp() throws Exception {
         mPipAnimationController = new PipAnimationController(
                 mContext, new PipSurfaceTransactionHelper(mContext));
+        mLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("FakeLeash")
+                .build();
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7c6343c..c891c11 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4131,6 +4131,17 @@
         // Let the old organizer know it has lost control.
         sendTaskVanished();
         mTaskOrganizer = organizer;
+
+        // If the task is not yet visible when it is added to the task organizer, then we should
+        // hide it to allow the task organizer to show it when it is properly reparented. We skip
+        // this for tasks created by the organizer because they can synchronously update the leash
+        // before new children are added to the task.
+        if (!mCreatedByOrganizer && organizer != null
+                && (!getHasBeenVisible() || !hasVisibleChildren())) {
+            getPendingTransaction().hide(getSurfaceControl());
+            commitPendingTransaction();
+        }
+
         sendTaskAppeared();
         onTaskOrganizerChanged();
         return true;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b641e4c..6e41aa5 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Intent;
@@ -38,6 +39,7 @@
 import android.window.ITaskOrganizerController;
 import android.window.WindowContainerToken;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
@@ -46,6 +48,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.WeakHashMap;
+import java.util.function.Consumer;
 
 /**
  * Stores the TaskOrganizers associated with a given windowing mode and
@@ -81,17 +84,95 @@
                 }
             }
         }
-    };
+    }
+
+    /**
+     * A wrapper class around ITaskOrganizer to ensure that the calls are made in the right
+     * lifecycle order since we may be updating the visibility of task surface controls in a pending
+     * transaction before they are presented to the task org.
+     */
+    private class TaskOrganizerCallbacks {
+        final WindowManagerService mService;
+        final ITaskOrganizer mTaskOrganizer;
+        final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
+
+        TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg,
+                Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
+            mService = wm;
+            mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
+            mTaskOrganizer = taskOrg;
+        }
+
+        IBinder getBinder() {
+            return mTaskOrganizer.asBinder();
+        }
+
+        void onTaskAppeared(Task task) {
+            final RunningTaskInfo taskInfo = task.getTaskInfo();
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.onTaskAppeared(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
+                }
+            });
+        }
+
+
+        void onTaskVanished(Task task) {
+            final RunningTaskInfo taskInfo = task.getTaskInfo();
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.onTaskVanished(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskVanished callback", e);
+                }
+            });
+        }
+
+        void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                if (!task.isOrganized()) {
+                    // This is safe to ignore if the task is no longer organized
+                    return;
+                }
+                try {
+                    mTaskOrganizer.onTaskInfoChanged(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskInfoChanged callback", e);
+                }
+            });
+        }
+
+        void onBackPressedOnTaskRoot(Task task) {
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                if (!task.isOrganized()) {
+                    // This is safe to ignore if the task is no longer organized
+                    return;
+                }
+                try {
+                   mTaskOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
+                } catch (Exception e) {
+                    Slog.e(TAG, "Exception sending onBackPressedOnTaskRoot callback", e);
+                }
+            });
+        }
+    }
 
     private class TaskOrganizerState {
-        private final ITaskOrganizer mOrganizer;
+        private final TaskOrganizerCallbacks mOrganizer;
         private final DeathRecipient mDeathRecipient;
         private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
         private final int mUid;
         private boolean mInterceptBackPressedOnTaskRoot;
 
         TaskOrganizerState(ITaskOrganizer organizer, int uid) {
-            mOrganizer = organizer;
+            final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
+                    mDeferTaskOrgCallbacksConsumer != null
+                            ? mDeferTaskOrgCallbacksConsumer
+                            : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
+            mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer,
+                    deferTaskOrgCallbacksConsumer);
             mDeathRecipient = new DeathRecipient(organizer);
             try {
                 organizer.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -107,26 +188,18 @@
 
         void addTask(Task t) {
             mOrganizedTasks.add(t);
-            try {
-                mOrganizer.onTaskAppeared(t.getTaskInfo());
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception sending taskAppeared callback" + e);
-            }
+            mOrganizer.onTaskAppeared(t);
         }
 
         void removeTask(Task t) {
-            try {
-                mOrganizer.onTaskVanished(t.getTaskInfo());
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception sending taskVanished callback" + e);
-            }
             mOrganizedTasks.remove(t);
+            mOrganizer.onTaskVanished(t);
         }
 
         void dispose() {
             releaseTasks();
             for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
-                mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.asBinder());
+                mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
             }
         }
 
@@ -139,7 +212,7 @@
         }
 
         void unlinkDeath() {
-            mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
+            mOrganizer.getBinder().unlinkToDeath(mDeathRecipient, 0);
         }
     }
 
@@ -149,9 +222,10 @@
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
 
-    final ActivityTaskManagerService mService;
+    private final ActivityTaskManagerService mService;
 
-    RunningTaskInfo mTmpTaskInfo;
+    private RunningTaskInfo mTmpTaskInfo;
+    private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
     TaskOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
@@ -163,6 +237,15 @@
     }
 
     /**
+     * Specifies the consumer to run to defer the task org callbacks. Can be overridden while
+     * testing to allow the callbacks to be sent synchronously.
+     */
+    @VisibleForTesting
+    public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
+        mDeferTaskOrgCallbacksConsumer = consumer;
+    }
+
+    /**
      * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
      * If there was already a TaskOrganizer for this windowing mode it will be evicted
      * but will continue to organize it's existing tasks.
@@ -253,7 +336,7 @@
         if (state == null) {
             return null;
         }
-        return state.mOrganizer;
+        return state.mOrganizer.mTaskOrganizer;
     }
 
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
@@ -358,11 +441,10 @@
         // change.
         mTmpTaskInfo = null;
 
-        if (task.mTaskOrganizer != null) {
-            try {
-                task.mTaskOrganizer.onTaskInfoChanged(newInfo);
-            } catch (RemoteException e) {
-            }
+        if (task.isOrganized()) {
+            final TaskOrganizerState state = mTaskOrganizerStates.get(
+                    task.mTaskOrganizer.asBinder());
+            state.mOrganizer.onTaskInfoChanged(task, newInfo);
         }
     }
 
@@ -517,11 +599,7 @@
             return false;
         }
 
-        try {
-            state.mOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending interceptBackPressedOnTaskRoot callback" + e);
-        }
+        state.mOrganizer.onBackPressedOnTaskRoot(task);
         return true;
     }
 
@@ -538,7 +616,7 @@
                 final TaskOrganizerState state =  mTaskOrganizerStates.get(taskOrgs.get(j));
                 final ArrayList<Task> tasks = state.mOrganizedTasks;
                 pw.print(innerPrefix + "    ");
-                pw.println(state.mOrganizer + " uid=" + state.mUid + ":");
+                pw.println(state.mOrganizer.mTaskOrganizer + " uid=" + state.mUid + ":");
                 for (int k = 0; k < tasks.size(); k++) {
                     pw.println(innerPrefix + "      " + tasks.get(k));
                 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 58119c2..037b9c8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2180,6 +2180,11 @@
 
     private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
                                     boolean isVoiceInteraction) {
+        if (isOrganized()) {
+            // Defer to the task organizer to run animations
+            return null;
+        }
+
         final DisplayContent displayContent = getDisplayContent();
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final int width = displayInfo.appWidth;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 6ef714e..2dbfd06 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -69,6 +69,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -117,6 +119,13 @@
         return createTaskStackOnDisplay(mDisplayContent);
     }
 
+    @Before
+    public void setUp() {
+        // We defer callbacks since we need to adjust task surface visibility, but for these tests,
+        // just run the callbacks synchronously
+        mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run());
+    }
+
     @Test
     public void testAppearVanish() throws RemoteException {
         final ActivityStack stack = createStack();