Add task organizer based task embedder

- Split TaskEmbedder into its current VirtualDisplay implementation
  and an implementation that uses task org to create and manage
  the task
- Use the task org embedder implementation in separate bubble task view
- Skip task org tasks from triggering task resizing
- Add task org callback for back press on task root if requested

Bug: 148977538
Test: atest CtsWindowManagerDeviceTestCases:ActivityViewTest
Test: atest WmTests:TaskOrganizerTests
Change-Id: Id422bb2547197c617f914ed7cf5085e02a1c3fb5
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index aaab6b4..073b8d0 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -69,6 +69,7 @@
     // For Host
     private final Point mWindowPosition = new Point();
     private final int[] mTmpArray = new int[2];
+    private final Rect mTmpRect = new Rect();
     private final Matrix mScreenSurfaceMatrix = new Matrix();
     private final Region mTapExcludeRegion = new Region();
 
@@ -84,10 +85,14 @@
         this(context, attrs, defStyle, false /*singleTaskInstance*/);
     }
 
-    public ActivityView(
-            Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
+    public ActivityView(Context context, AttributeSet attrs, int defStyle,
+            boolean singleTaskInstance) {
         super(context, attrs, defStyle);
-        mTaskEmbedder = new TaskEmbedder(getContext(), this, singleTaskInstance);
+        if (useTaskOrganizer()) {
+            mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
+        } else {
+            mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance);
+        }
         mSurfaceView = new SurfaceView(context);
         // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
         // as master to synchronize surface view's alpha value.
@@ -129,6 +134,12 @@
         public void onTaskCreated(int taskId, ComponentName componentName) { }
 
         /**
+         * Called when a task visibility changes.
+         * @hide
+         */
+        public void onTaskVisibilityChanged(int taskId, boolean visible) { }
+
+        /**
          * Called when a task is moved to the front of the stack inside the container.
          * This is a filtered version of {@link TaskStackListener}
          */
@@ -139,6 +150,12 @@
          * This is a filtered version of {@link TaskStackListener}
          */
         public void onTaskRemovalStarted(int taskId) { }
+
+        /**
+         * Called when back is pressed on the root activity of the task.
+         * @hide
+         */
+        public void onBackPressedOnTaskRoot(int taskId) { }
     }
 
     /**
@@ -370,10 +387,8 @@
 
     @Override
     public boolean gatherTransparentRegion(Region region) {
-        // The tap exclude region may be affected by any view on top of it, so we detect the
-        // possible change by monitoring this function.
-        mTaskEmbedder.notifyBoundsChanged();
-        return super.gatherTransparentRegion(region);
+        return mTaskEmbedder.gatherTransparentRegion(region)
+                || super.gatherTransparentRegion(region);
     }
 
     private class SurfaceCallback implements SurfaceHolder.Callback {
@@ -432,7 +447,6 @@
             Log.e(TAG, "Failed to initialize ActivityView");
             return false;
         }
-        mTmpTransaction.show(mTaskEmbedder.getSurfaceControl()).apply();
         return true;
     }
 
@@ -520,6 +534,13 @@
 
     /** @hide */
     @Override
+    public Rect getScreenBounds() {
+        getBoundsOnScreen(mTmpRect);
+        return mTmpRect;
+    }
+
+    /** @hide */
+    @Override
     public IWindow getWindow() {
         return super.getWindow();
     }
@@ -530,6 +551,15 @@
         return super.canReceivePointerEvents();
     }
 
+    /**
+     * Overridden by instances that require the use of the task organizer implementation instead of
+     * the virtual display implementation.  Not for general use.
+     * @hide
+     */
+    protected boolean useTaskOrganizer() {
+        return false;
+    }
+
     private final class StateCallbackAdapter implements TaskEmbedder.Listener {
         private final StateCallback mCallback;
 
@@ -553,6 +583,11 @@
         }
 
         @Override
+        public void onTaskVisibilityChanged(int taskId, boolean visible) {
+            mCallback.onTaskVisibilityChanged(taskId, visible);
+        }
+
+        @Override
         public void onTaskMovedToFront(int taskId) {
             mCallback.onTaskMovedToFront(taskId);
         }
@@ -561,5 +596,10 @@
         public void onTaskRemovalStarted(int taskId) {
             mCallback.onTaskRemovalStarted(taskId);
         }
+
+        @Override
+        public void onBackPressedOnTaskRoot(int taskId) {
+            mCallback.onBackPressedOnTaskRoot(taskId);
+        }
     }
 }
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/app/TaskEmbedder.java
index b8ad308..10c11f2 100644
--- a/core/java/android/app/TaskEmbedder.java
+++ b/core/java/android/app/TaskEmbedder.java
@@ -16,11 +16,9 @@
 
 package android.app;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.INVALID_DISPLAY;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -33,38 +31,24 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.hardware.input.InputManager;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.view.Display;
 import android.view.IWindow;
 import android.view.IWindowManager;
-import android.view.IWindowSession;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.SurfaceControl;
-import android.view.WindowManagerGlobal;
-import android.view.inputmethod.InputMethodManager;
 
 import dalvik.system.CloseGuard;
 
-import java.util.List;
-
 /**
  * A component which handles embedded display of tasks within another window. The embedded task can
  * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
  *
  * @hide
  */
-public class TaskEmbedder {
+public abstract class TaskEmbedder {
     private static final String TAG = "TaskEmbedder";
-    private static final String DISPLAY_NAME = "TaskVirtualDisplay";
 
     /**
      * A component which will host the task.
@@ -82,6 +66,9 @@
         /** @return the x/y offset from the origin of the window to the surface */
         Point getPositionInWindow();
 
+        /** @return the screen bounds of the host */
+        Rect getScreenBounds();
+
         /** @return whether this surface is able to receive pointer events */
         boolean canReceivePointerEvents();
 
@@ -96,6 +83,11 @@
          * fill unpainted areas if necessary.
          */
         void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor);
+
+        /**
+         * Posts a runnable to be run on the host's handler.
+         */
+        boolean post(Runnable r);
     }
 
     /**
@@ -111,27 +103,29 @@
         /** Called when a task is created inside the container. */
         default void onTaskCreated(int taskId, ComponentName name) {}
 
+        /** Called when a task visibility changes. */
+        default void onTaskVisibilityChanged(int taskId, boolean visible) {}
+
         /** Called when a task is moved to the front of the stack inside the container. */
         default void onTaskMovedToFront(int taskId) {}
 
         /** Called when a task is about to be removed from the stack inside the container. */
         default void onTaskRemovalStarted(int taskId) {}
+
+        /** Called when a task is created inside the container. */
+        default void onBackPressedOnTaskRoot(int taskId) {}
     }
 
-    private IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService();
+    protected IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService();
 
-    private final Context mContext;
-    private TaskEmbedder.Host mHost;
-    private int mDisplayDensityDpi;
-    private final boolean mSingleTaskInstance;
-    private SurfaceControl.Transaction mTransaction;
-    private SurfaceControl mSurfaceControl;
-    private VirtualDisplay mVirtualDisplay;
-    private Insets mForwardedInsets;
-    private TaskStackListener mTaskStackListener;
-    private Listener mListener;
-    private boolean mOpened; // Protected by mGuard.
-    private DisplayMetrics mTmpDisplayMetrics;
+    protected final Context mContext;
+    protected TaskEmbedder.Host mHost;
+
+    protected SurfaceControl.Transaction mTransaction;
+    protected SurfaceControl mSurfaceControl;
+    protected TaskStackListener mTaskStackListener;
+    protected Listener mListener;
+    protected boolean mOpened; // Protected by mGuard.
 
     private final CloseGuard mGuard = CloseGuard.get();
 
@@ -141,51 +135,25 @@
      *
      * @param context the context
      * @param host the host for this embedded task
-     * @param singleTaskInstance whether to apply a single-task constraint to this container
      */
-    public TaskEmbedder(Context context, TaskEmbedder.Host host, boolean singleTaskInstance) {
+    public TaskEmbedder(Context context, TaskEmbedder.Host host) {
         mContext = context;
         mHost = host;
-        mSingleTaskInstance = singleTaskInstance;
     }
 
     /**
-     * Whether this container has been initialized.
-     *
-     * @return true if initialized
-     */
-    public boolean isInitialized() {
-        return mVirtualDisplay != null;
-    }
-
-    /**
-     * Initialize this container.
+     * Initialize this container when the ActivityView's SurfaceView is first created.
      *
      * @param parent the surface control for the parent surface
      * @return true if initialized successfully
      */
     public boolean initialize(SurfaceControl parent) {
-        if (mVirtualDisplay != null) {
+        if (isInitialized()) {
             throw new IllegalStateException("Trying to initialize for the second time.");
         }
 
         mTransaction = new SurfaceControl.Transaction();
-
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        mDisplayDensityDpi = getBaseDisplayDensity();
-
-        mVirtualDisplay = displayManager.createVirtualDisplay(
-                DISPLAY_NAME + "@" + System.identityHashCode(this), mHost.getWidth(),
-                mHost.getHeight(), mDisplayDensityDpi, null,
-                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-                        | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
-
-        if (mVirtualDisplay == null) {
-            Log.e(TAG, "Failed to initialize TaskEmbedder");
-            return false;
-        }
-
-        // Create a container surface to which the DisplayContent will be reparented
+        // Create a container surface to which the task content will be reparented
         final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
         mSurfaceControl = new SurfaceControl.Builder()
                 .setContainerLayer()
@@ -193,40 +161,96 @@
                 .setName(name)
                 .build();
 
-        final int displayId = getDisplayId();
-
-        final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-        try {
-            // TODO: Find a way to consolidate these calls to the server.
-            WindowManagerGlobal.getWindowSession().reparentDisplayContent(
-                    mHost.getWindow(), mSurfaceControl, displayId);
-            wm.dontOverrideDisplayInfo(displayId);
-            if (mSingleTaskInstance) {
-                mContext.getSystemService(ActivityTaskManager.class)
-                        .setDisplayToSingleTaskInstance(displayId);
-            }
-            setForwardedInsets(mForwardedInsets);
-            if (mHost.getWindow() != null) {
-                updateLocationAndTapExcludeRegion();
-            }
-            mTaskStackListener = new TaskStackListenerImpl();
-            try {
-                mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to register task stack listener", e);
-            }
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        if (!onInitialize()) {
+            return false;
         }
-        if (mListener != null && mVirtualDisplay != null) {
+
+        mTaskStackListener = createTaskStackListener();
+        try {
+            mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register task stack listener", e);
+        }
+        if (mListener != null && isInitialized()) {
             mListener.onInitialized();
         }
         mOpened = true;
         mGuard.open("release");
+        mTransaction.show(getSurfaceControl()).apply();
         return true;
     }
 
     /**
+     * @return the task stack listener for this embedder
+     */
+    public abstract TaskStackListener createTaskStackListener();
+
+    /**
+     * Whether this container has been initialized.
+     *
+     * @return true if initialized
+     */
+    public abstract boolean isInitialized();
+
+    /**
+     * Called when the task embedder should be initialized.
+     * @return whether to report whether the embedder was initialized.
+     */
+    public abstract boolean onInitialize();
+
+    /**
+     * Called when the task embedder should be released.
+     * @return whether to report whether the embedder was released.
+     */
+    protected abstract boolean onRelease();
+
+    /**
+     * Starts presentation of tasks in this container.
+     */
+    public abstract void start();
+
+    /**
+     * Stops presentation of tasks in this container.
+     */
+    public abstract void stop();
+
+    /**
+     * This should be called whenever the position or size of the surface changes
+     * or if touchable areas above the surface are added or removed.
+     */
+    public abstract void notifyBoundsChanged();
+
+    /**
+     * Called to update the dimensions whenever the host size changes.
+     *
+     * @param width the new width of the surface
+     * @param height the new height of the surface
+     */
+    public void resizeTask(int width, int height) {
+        // Do nothing
+    }
+
+    /**
+     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
+     * virtual display.
+     */
+    public abstract void performBackPress();
+
+    /**
+     * An opaque unique identifier for this task surface among others being managed by the app.
+     */
+    public abstract int getId();
+
+    /**
+     * Calculates and updates the {@param region} with the transparent region for this task
+     * embedder.
+     */
+    public boolean gatherTransparentRegion(Region region) {
+        // Do nothing
+        return false;
+    }
+
+    /**
      * Returns the surface control for the task surface. This should be parented to a screen
      * surface for display/embedding purposes.
      *
@@ -236,34 +260,17 @@
         return mSurfaceControl;
     }
 
+    public int getDisplayId() {
+        return INVALID_DISPLAY;
+    }
+
     /**
-     * Set forwarded insets on the virtual display.
+     * Set forwarded insets on the task content.
      *
      * @see IWindowManager#setForwardedInsets
      */
     public void setForwardedInsets(Insets insets) {
-        mForwardedInsets = insets;
-        if (mVirtualDisplay == null) {
-            return;
-        }
-        try {
-            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-            wm.setForwardedInsets(getDisplayId(), mForwardedInsets);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
-    }
-
-    /** An opaque unique identifier for this task surface among others being managed by the app. */
-    public int getId() {
-        return getDisplayId();
-    }
-
-    int getDisplayId() {
-        if (mVirtualDisplay != null) {
-            return mVirtualDisplay.getDisplay().getDisplayId();
-        }
-        return Display.INVALID_DISPLAY;
+        // Do nothing
     }
 
     /**
@@ -372,166 +379,19 @@
      * Check if container is ready to launch and modify {@param options} to target the virtual
      * display, creating them if necessary.
      */
-    private ActivityOptions prepareActivityOptions(ActivityOptions options) {
-        if (mVirtualDisplay == null) {
+    @CallSuper
+    protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
+        if (!isInitialized()) {
             throw new IllegalStateException(
                     "Trying to start activity before ActivityView is ready.");
         }
         if (options == null) {
             options = ActivityOptions.makeBasic();
         }
-        options.setLaunchDisplayId(getDisplayId());
-        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        options.setTaskAlwaysOnTop(true);
         return options;
     }
 
     /**
-     * Stops presentation of tasks in this container.
-     */
-    public void stop() {
-        if (mVirtualDisplay != null) {
-            mVirtualDisplay.setDisplayState(false);
-            clearActivityViewGeometryForIme();
-            clearTapExcludeRegion();
-        }
-    }
-
-    /**
-     * Starts presentation of tasks in this container.
-     */
-    public void start() {
-        if (mVirtualDisplay != null) {
-            mVirtualDisplay.setDisplayState(true);
-            updateLocationAndTapExcludeRegion();
-        }
-    }
-
-    /**
-     * This should be called whenever the position or size of the surface changes
-     * or if touchable areas above the surface are added or removed.
-     */
-    public void notifyBoundsChanged() {
-        updateLocationAndTapExcludeRegion();
-    }
-
-    /**
-     * Updates position and bounds information needed by WM and IME to manage window
-     * focus and touch events properly.
-     * <p>
-     * This should be called whenever the position or size of the surface changes
-     * or if touchable areas above the surface are added or removed.
-     */
-    private void updateLocationAndTapExcludeRegion() {
-        if (mVirtualDisplay == null || mHost.getWindow() == null) {
-            return;
-        }
-        reportLocation(mHost.getScreenToTaskMatrix(), mHost.getPositionInWindow());
-        applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion());
-    }
-
-    /**
-     * Call to update the position and transform matrix for the embedded surface.
-     * <p>
-     * This should not normally be called directly, but through
-     * {@link #updateLocationAndTapExcludeRegion()}. This method
-     * is provided as an optimization when managing multiple TaskSurfaces within a view.
-     *
-     * @param screenToViewMatrix the matrix/transform from screen space to view space
-     * @param positionInWindow the window-relative position of the surface
-     *
-     * @see InputMethodManager#reportActivityView(int, Matrix)
-     */
-    private void reportLocation(Matrix screenToViewMatrix, Point positionInWindow) {
-        try {
-            final int displayId = getDisplayId();
-            mContext.getSystemService(InputMethodManager.class)
-                    .reportActivityView(displayId, screenToViewMatrix);
-            IWindowSession session = WindowManagerGlobal.getWindowSession();
-            session.updateDisplayContentLocation(mHost.getWindow(), positionInWindow.x,
-                    positionInWindow.y, displayId);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Call to update the tap exclude region for the window.
-     * <p>
-     * This should not normally be called directly, but through
-     * {@link #updateLocationAndTapExcludeRegion()}. This method
-     * is provided as an optimization when managing multiple TaskSurfaces within a view.
-     *
-     * @see IWindowSession#updateTapExcludeRegion(IWindow, Region)
-     */
-    private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) {
-        try {
-            IWindowSession session = WindowManagerGlobal.getWindowSession();
-            session.updateTapExcludeRegion(window, tapExcludeRegion);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * @see InputMethodManager#reportActivityView(int, Matrix)
-     */
-    private void clearActivityViewGeometryForIme() {
-        final int displayId = getDisplayId();
-        mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
-    }
-
-    /**
-     * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}.
-     */
-    private void clearTapExcludeRegion() {
-        if (mHost.getWindow() == null) {
-            Log.w(TAG, "clearTapExcludeRegion: not attached to window!");
-            return;
-        }
-        applyTapExcludeRegion(mHost.getWindow(), null);
-    }
-
-    /**
-     * Called to update the dimensions whenever the host size changes.
-     *
-     * @param width the new width of the surface
-     * @param height the new height of the surface
-     */
-    public void resizeTask(int width, int height) {
-        mDisplayDensityDpi = getBaseDisplayDensity();
-        if (mVirtualDisplay != null) {
-            mVirtualDisplay.resize(width, height, mDisplayDensityDpi);
-        }
-    }
-
-    /**
-     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
-     * virtual display.
-     */
-    public void performBackPress() {
-        if (mVirtualDisplay == null) {
-            return;
-        }
-        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
-        final InputManager im = InputManager.getInstance();
-        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
-                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
-        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
-                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
-    }
-
-    private static KeyEvent createKeyEvent(int action, int code, int displayId) {
-        long when = SystemClock.uptimeMillis();
-        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
-                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
-                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
-                InputDevice.SOURCE_KEYBOARD);
-        ev.setDisplayId(displayId);
-        return ev;
-    }
-
-    /**
      * Releases the resources for this TaskEmbedder. Tasks will no longer be launchable
      * within this container.
      *
@@ -539,9 +399,8 @@
      * triggered and before {@link Listener#onReleased()}.
      */
     public void release() {
-        if (mVirtualDisplay == null) {
-            throw new IllegalStateException(
-                    "Trying to release container that is not initialized.");
+        if (!isInitialized()) {
+            throw new IllegalStateException("Trying to release container that is not initialized.");
         }
         performRelease();
     }
@@ -550,14 +409,11 @@
         if (!mOpened) {
             return false;
         }
+
         mTransaction.reparent(mSurfaceControl, null).apply();
         mSurfaceControl.release();
 
-        // Clear activity view geometry for IME on this display
-        clearActivityViewGeometryForIme();
-
-        // Clear tap-exclude region (if any) for this window.
-        clearTapExcludeRegion();
+        boolean reportReleased = onRelease();
 
         if (mTaskStackListener != null) {
             try {
@@ -568,14 +424,6 @@
             mTaskStackListener = null;
         }
 
-        boolean reportReleased = false;
-        if (mVirtualDisplay != null) {
-            mVirtualDisplay.release();
-            mVirtualDisplay = null;
-            reportReleased = true;
-
-        }
-
         if (mListener != null && reportReleased) {
             mListener.onReleased();
         }
@@ -595,98 +443,4 @@
             super.finalize();
         }
     }
-
-    /** Get density of the hosting display. */
-    private int getBaseDisplayDensity() {
-        if (mTmpDisplayMetrics == null) {
-            mTmpDisplayMetrics = new DisplayMetrics();
-        }
-        mContext.getDisplayNoVerify().getRealMetrics(mTmpDisplayMetrics);
-        return mTmpDisplayMetrics.densityDpi;
-    }
-
-    /**
-     * A task change listener that detects background color change of the topmost stack on our
-     * virtual display and updates the background of the surface view. This background will be shown
-     * when surface view is resized, but the app hasn't drawn its content in new size yet.
-     * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
-     * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
-     * when needing to also bring the host Activity to the foreground at the same time.
-     */
-    private class TaskStackListenerImpl extends TaskStackListener {
-
-        @Override
-        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
-            if (!isInitialized()
-                    || taskInfo.displayId != getDisplayId()) {
-                return;
-            }
-
-            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
-            if (stackInfo == null) {
-                return;
-            }
-            // Found the topmost stack on target display. Now check if the topmost task's
-            // description changed.
-            if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
-                mHost.onTaskBackgroundColorChanged(TaskEmbedder.this,
-                        taskInfo.taskDescription.getBackgroundColor());
-            }
-        }
-
-        @Override
-        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
-            if (!isInitialized() || mListener == null
-                    || taskInfo.displayId != getDisplayId()) {
-                return;
-            }
-
-            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
-            // if StackInfo was null or unrelated to the "move to front" then there's no use
-            // notifying the callback
-            if (stackInfo != null
-                    && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
-                mListener.onTaskMovedToFront(taskInfo.taskId);
-            }
-        }
-
-        @Override
-        public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
-            if (mListener == null || !isInitialized()) {
-                return;
-            }
-
-            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
-            // if StackInfo was null or unrelated to the task creation then there's no use
-            // notifying the callback
-            if (stackInfo != null
-                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
-                mListener.onTaskCreated(taskId, componentName);
-            }
-        }
-
-        @Override
-        public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
-            if (mListener == null || !isInitialized()
-                    || taskInfo.displayId != getDisplayId()) {
-                return;
-            }
-            mListener.onTaskRemovalStarted(taskInfo.taskId);
-        }
-
-        private ActivityManager.StackInfo getTopMostStackInfo() throws RemoteException {
-            // Find the topmost task on our virtual display - it will define the background
-            // color of the surface view during resizing.
-            final int displayId = getDisplayId();
-            final List<ActivityManager.StackInfo> stackInfoList =
-                    mActivityTaskManager.getAllStackInfosOnDisplay(displayId);
-            if (stackInfoList.isEmpty()) {
-                return null;
-            }
-            return stackInfoList.get(0);
-        }
-    }
 }
diff --git a/core/java/android/app/TaskOrganizerTaskEmbedder.java b/core/java/android/app/TaskOrganizerTaskEmbedder.java
new file mode 100644
index 0000000..adc0792
--- /dev/null
+++ b/core/java/android/app/TaskOrganizerTaskEmbedder.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizer;
+import android.window.IWindowContainer;
+import android.window.WindowContainerTransaction;
+import android.window.WindowOrganizer;
+import android.window.WindowOrganizer.TaskOrganizer;
+
+/**
+ * A component which handles embedded display of tasks within another window. The embedded task can
+ * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
+ *
+ * @hide
+ */
+public class TaskOrganizerTaskEmbedder extends TaskEmbedder {
+    private static final String TAG = "TaskOrgTaskEmbedder";
+    private static final boolean DEBUG = false;
+
+    private ITaskOrganizer.Stub mTaskOrganizer;
+    private ActivityManager.RunningTaskInfo mTaskInfo;
+    private IWindowContainer mTaskToken;
+    private SurfaceControl mTaskLeash;
+    private boolean mPendingNotifyBoundsChanged;
+
+    /**
+     * Constructs a new TaskEmbedder.
+     *
+     * @param context the context
+     * @param host the host for this embedded task
+     */
+    public TaskOrganizerTaskEmbedder(Context context, TaskOrganizerTaskEmbedder.Host host) {
+        super(context, host);
+    }
+
+    @Override
+    public TaskStackListener createTaskStackListener() {
+        return new TaskStackListenerImpl();
+    }
+
+    /**
+     * Whether this container has been initialized.
+     *
+     * @return true if initialized
+     */
+    @Override
+    public boolean isInitialized() {
+        return mTaskOrganizer != null;
+    }
+
+    @Override
+    public boolean onInitialize() {
+        if (DEBUG) {
+            log("onInitialize");
+        }
+        // Register the task organizer
+        mTaskOrganizer =  new TaskOrganizerImpl();
+        try {
+            // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW
+            // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
+            // infrastructure is ready.
+            TaskOrganizer.registerOrganizer(mTaskOrganizer, WINDOWING_MODE_MULTI_WINDOW);
+            TaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskOrganizer, true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to initialize TaskEmbedder", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected boolean onRelease() {
+        if (DEBUG) {
+            log("onRelease");
+        }
+        if (!isInitialized()) {
+            return false;
+        }
+        try {
+            TaskOrganizer.unregisterOrganizer(mTaskOrganizer);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove task");
+        }
+        resetTaskInfo();
+        return true;
+    }
+
+    /**
+     * Starts presentation of tasks in this container.
+     */
+    @Override
+    public void start() {
+        if (DEBUG) {
+            log("start");
+        }
+        if (!isInitialized()) {
+            return;
+        }
+        if (mTaskToken == null) {
+            return;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(mTaskToken, false /* hidden */);
+        try {
+            WindowOrganizer.applyTransaction(wct);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unset hidden in transaction");
+        }
+        // TODO(b/151449487): Only call callback once we enable synchronization
+        if (mListener != null) {
+            mListener.onTaskVisibilityChanged(getTaskId(), true);
+        }
+    }
+
+    /**
+     * Stops presentation of tasks in this container.
+     */
+    @Override
+    public void stop() {
+        if (DEBUG) {
+            log("stop");
+        }
+        if (!isInitialized()) {
+            return;
+        }
+        if (mTaskToken == null) {
+            return;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(mTaskToken, true /* hidden */);
+        try {
+            WindowOrganizer.applyTransaction(wct);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to set hidden in transaction");
+        }
+        // TODO(b/151449487): Only call callback once we enable synchronization
+        if (mListener != null) {
+            mListener.onTaskVisibilityChanged(getTaskId(), false);
+        }
+    }
+
+    /**
+     * This should be called whenever the position or size of the surface changes
+     * or if touchable areas above the surface are added or removed.
+     */
+    @Override
+    public void notifyBoundsChanged() {
+        if (DEBUG) {
+            log("notifyBoundsChanged: screenBounds=" + mHost.getScreenBounds());
+        }
+        if (mTaskToken == null) {
+            mPendingNotifyBoundsChanged = true;
+            return;
+        }
+        mPendingNotifyBoundsChanged = false;
+
+        // Update based on the screen bounds
+        Rect screenBounds = mHost.getScreenBounds();
+        if (screenBounds.left < 0 || screenBounds.top < 0) {
+            screenBounds.offsetTo(0, 0);
+        }
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mTaskToken, screenBounds);
+        try {
+            // TODO(b/151449487): Enable synchronization
+            WindowOrganizer.applyTransaction(wct);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to set bounds in transaction");
+        }
+    }
+
+    /**
+     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
+     * virtual display.
+     */
+    @Override
+    public void performBackPress() {
+        // Do nothing, the task org task should already have focus if the caller is not focused
+        return;
+    }
+
+    /** An opaque unique identifier for this task surface among others being managed by the app. */
+    @Override
+    public int getId() {
+        return getTaskId();
+    }
+
+    /**
+     * Check if container is ready to launch and create {@link ActivityOptions} to target the
+     * virtual display.
+     * @param options The existing options to amend, or null if the caller wants new options to be
+     *                created
+     */
+    @Override
+    protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
+        options = super.prepareActivityOptions(options);
+        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        return options;
+    }
+
+    private int getTaskId() {
+        return mTaskInfo != null
+                ? mTaskInfo.taskId
+                : INVALID_TASK_ID;
+    }
+
+    private void resetTaskInfo() {
+        if (DEBUG) {
+            log("resetTaskInfo");
+        }
+        mTaskInfo = null;
+        mTaskToken = null;
+        mTaskLeash = null;
+    }
+
+    private void log(String msg) {
+        Log.d(TAG, "[" + System.identityHashCode(this) + "] " + msg);
+    }
+
+    /**
+     * A task change listener that detects background color change of the topmost stack on our
+     * virtual display and updates the background of the surface view. This background will be shown
+     * when surface view is resized, but the app hasn't drawn its content in new size yet.
+     * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
+     * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
+     * when needing to also bring the host Activity to the foreground at the same time.
+     */
+    private class TaskStackListenerImpl extends TaskStackListener {
+
+        @Override
+        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (!isInitialized()) {
+                return;
+            }
+            if (taskInfo.taskId == mTaskInfo.taskId) {
+                mTaskInfo.taskDescription = taskInfo.taskDescription;
+                mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this,
+                        taskInfo.taskDescription.getBackgroundColor());
+            }
+        }
+    }
+
+    private class TaskOrganizerImpl extends ITaskOrganizer.Stub {
+        @Override
+        public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (DEBUG) {
+                log("taskAppeared: " + taskInfo.taskId);
+            }
+
+            // TODO: Ensure visibility/alpha of the leash in its initial state?
+            mTaskInfo = taskInfo;
+            mTaskToken = taskInfo.token;
+            mTaskLeash = mTaskToken.getLeash();
+            mTransaction.reparent(mTaskLeash, mSurfaceControl)
+                    .show(mSurfaceControl).apply();
+            if (mPendingNotifyBoundsChanged) {
+                // TODO: Either defer show or hide and synchronize show with the resize
+                notifyBoundsChanged();
+            }
+            mHost.post(() -> mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this,
+                    taskInfo.taskDescription.getBackgroundColor()));
+
+            if (mListener != null) {
+                mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+            }
+        }
+
+        @Override
+        public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (DEBUG) {
+                log("taskVanished: " + taskInfo.taskId);
+            }
+
+            if (mTaskToken != null && (taskInfo == null
+                    || mTaskToken.asBinder().equals(taskInfo.token.asBinder()))) {
+                if (mListener != null) {
+                    mListener.onTaskRemovalStarted(taskInfo.taskId);
+                }
+                resetTaskInfo();
+            }
+        }
+
+        @Override
+        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            // Do nothing
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (mListener != null) {
+                mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/VirtualDisplayTaskEmbedder.java b/core/java/android/app/VirtualDisplayTaskEmbedder.java
new file mode 100644
index 0000000..7ad8f22
--- /dev/null
+++ b/core/java/android/app/VirtualDisplayTaskEmbedder.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.WindowManagerGlobal;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.List;
+
+/**
+ * A component which handles embedded display of tasks within another window. The embedded task can
+ * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
+ *
+ * @hide
+ */
+public class VirtualDisplayTaskEmbedder extends TaskEmbedder {
+    private static final String TAG = "VirDispTaskEmbedder";
+    private static final String DISPLAY_NAME = "TaskVirtualDisplay";
+
+    // For Virtual Displays
+    private int mDisplayDensityDpi;
+    private final boolean mSingleTaskInstance;
+    private VirtualDisplay mVirtualDisplay;
+    private Insets mForwardedInsets;
+    private DisplayMetrics mTmpDisplayMetrics;
+
+    /**
+     * Constructs a new TaskEmbedder.
+     *
+     * @param context the context
+     * @param host the host for this embedded task
+     * @param singleTaskInstance whether to apply a single-task constraint to this container,
+     *                           only applicable if virtual displays are used
+     */
+    VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host,
+            boolean singleTaskInstance) {
+        super(context, host);
+        mSingleTaskInstance = singleTaskInstance;
+    }
+
+    @Override
+    public TaskStackListener createTaskStackListener() {
+        return new TaskStackListenerImpl();
+    }
+
+    /**
+     * Whether this container has been initialized.
+     *
+     * @return true if initialized
+     */
+    @Override
+    public boolean isInitialized() {
+        return mVirtualDisplay != null;
+    }
+
+    @Override
+    public boolean onInitialize() {
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        mDisplayDensityDpi = getBaseDisplayDensity();
+        mVirtualDisplay = displayManager.createVirtualDisplay(
+                DISPLAY_NAME + "@" + System.identityHashCode(this), mHost.getWidth(),
+                mHost.getHeight(), mDisplayDensityDpi, null,
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                        | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
+
+        if (mVirtualDisplay == null) {
+            Log.e(TAG, "Failed to initialize TaskEmbedder");
+            return false;
+        }
+
+        try {
+            // TODO: Find a way to consolidate these calls to the server.
+            final int displayId = getDisplayId();
+            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+            WindowManagerGlobal.getWindowSession().reparentDisplayContent(
+                    mHost.getWindow(), mSurfaceControl, displayId);
+            wm.dontOverrideDisplayInfo(displayId);
+            if (mSingleTaskInstance) {
+                mContext.getSystemService(ActivityTaskManager.class)
+                        .setDisplayToSingleTaskInstance(displayId);
+            }
+            setForwardedInsets(mForwardedInsets);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+
+        if (mHost.getWindow() != null) {
+            updateLocationAndTapExcludeRegion();
+        }
+        return true;
+    }
+
+    @Override
+    protected boolean onRelease() {
+        // Clear activity view geometry for IME on this display
+        clearActivityViewGeometryForIme();
+
+        // Clear tap-exclude region (if any) for this window.
+        clearTapExcludeRegion();
+
+        if (isInitialized()) {
+            mVirtualDisplay.release();
+            mVirtualDisplay = null;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Starts presentation of tasks in this container.
+     */
+    @Override
+    public void start() {
+        if (isInitialized()) {
+            mVirtualDisplay.setDisplayState(true);
+            updateLocationAndTapExcludeRegion();
+        }
+    }
+
+    /**
+     * Stops presentation of tasks in this container.
+     */
+    @Override
+    public void stop() {
+        if (isInitialized()) {
+            mVirtualDisplay.setDisplayState(false);
+            clearActivityViewGeometryForIme();
+            clearTapExcludeRegion();
+        }
+    }
+
+    /**
+     * This should be called whenever the position or size of the surface changes
+     * or if touchable areas above the surface are added or removed.
+     */
+    @Override
+    public void notifyBoundsChanged() {
+        updateLocationAndTapExcludeRegion();
+    }
+
+    /**
+     * Called to update the dimensions whenever the host size changes.
+     *
+     * @param width the new width of the surface
+     * @param height the new height of the surface
+     */
+    @Override
+    public void resizeTask(int width, int height) {
+        mDisplayDensityDpi = getBaseDisplayDensity();
+        if (isInitialized()) {
+            mVirtualDisplay.resize(width, height, mDisplayDensityDpi);
+        }
+    }
+
+    /**
+     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
+     * virtual display.
+     */
+    @Override
+    public void performBackPress() {
+        if (!isInitialized()) {
+            return;
+        }
+        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+        final InputManager im = InputManager.getInstance();
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        // The tap exclude region may be affected by any view on top of it, so we detect the
+        // possible change by monitoring this function. The tap exclude region is only used
+        // for virtual displays.
+        notifyBoundsChanged();
+        return super.gatherTransparentRegion(region);
+    }
+
+    /** An opaque unique identifier for this task surface among others being managed by the app. */
+    @Override
+    public int getId() {
+        return getDisplayId();
+    }
+
+    @Override
+    public int getDisplayId() {
+        if (isInitialized()) {
+            return mVirtualDisplay.getDisplay().getDisplayId();
+        }
+        return INVALID_DISPLAY;
+    }
+
+    /**
+     * Check if container is ready to launch and create {@link ActivityOptions} to target the
+     * virtual display.
+     * @param options The existing options to amend, or null if the caller wants new options to be
+     *                created
+     */
+    @Override
+    protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
+        options = super.prepareActivityOptions(options);
+        options.setLaunchDisplayId(getDisplayId());
+        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        options.setTaskAlwaysOnTop(true);
+        return options;
+    }
+
+    /**
+     * Set forwarded insets on the virtual display.
+     *
+     * @see IWindowManager#setForwardedInsets
+     */
+    @Override
+    public void setForwardedInsets(Insets insets) {
+        mForwardedInsets = insets;
+        if (!isInitialized()) {
+            return;
+        }
+        try {
+            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+            wm.setForwardedInsets(getDisplayId(), mForwardedInsets);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Updates position and bounds information needed by WM and IME to manage window
+     * focus and touch events properly.
+     * <p>
+     * This should be called whenever the position or size of the surface changes
+     * or if touchable areas above the surface are added or removed.
+     */
+    private void updateLocationAndTapExcludeRegion() {
+        if (!isInitialized() || mHost.getWindow() == null) {
+            return;
+        }
+        reportLocation(mHost.getScreenToTaskMatrix(), mHost.getPositionInWindow());
+        applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion());
+    }
+
+    /**
+     * Call to update the position and transform matrix for the embedded surface.
+     * <p>
+     * This should not normally be called directly, but through
+     * {@link #updateLocationAndTapExcludeRegion()}. This method
+     * is provided as an optimization when managing multiple TaskSurfaces within a view.
+     *
+     * @param screenToViewMatrix the matrix/transform from screen space to view space
+     * @param positionInWindow the window-relative position of the surface
+     *
+     * @see InputMethodManager#reportActivityView(int, Matrix)
+     */
+    private void reportLocation(Matrix screenToViewMatrix, Point positionInWindow) {
+        try {
+            final int displayId = getDisplayId();
+            mContext.getSystemService(InputMethodManager.class)
+                    .reportActivityView(displayId, screenToViewMatrix);
+            IWindowSession session = WindowManagerGlobal.getWindowSession();
+            session.updateDisplayContentLocation(mHost.getWindow(), positionInWindow.x,
+                    positionInWindow.y, displayId);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Call to update the tap exclude region for the window.
+     * <p>
+     * This should not normally be called directly, but through
+     * {@link #updateLocationAndTapExcludeRegion()}. This method
+     * is provided as an optimization when managing multiple TaskSurfaces within a view.
+     *
+     * @see IWindowSession#updateTapExcludeRegion(IWindow, Region)
+     */
+    private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) {
+        try {
+            IWindowSession session = WindowManagerGlobal.getWindowSession();
+            session.updateTapExcludeRegion(window, tapExcludeRegion);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * @see InputMethodManager#reportActivityView(int, Matrix)
+     */
+    private void clearActivityViewGeometryForIme() {
+        final int displayId = getDisplayId();
+        mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
+    }
+
+    /**
+     * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}.
+     */
+    private void clearTapExcludeRegion() {
+        if (mHost.getWindow() == null) {
+            Log.w(TAG, "clearTapExcludeRegion: not attached to window!");
+            return;
+        }
+        applyTapExcludeRegion(mHost.getWindow(), null);
+    }
+
+    private static KeyEvent createKeyEvent(int action, int code, int displayId) {
+        long when = SystemClock.uptimeMillis();
+        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
+                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        ev.setDisplayId(displayId);
+        return ev;
+    }
+
+    /** Get density of the hosting display. */
+    private int getBaseDisplayDensity() {
+        if (mTmpDisplayMetrics == null) {
+            mTmpDisplayMetrics = new DisplayMetrics();
+        }
+        mContext.getDisplayNoVerify().getRealMetrics(mTmpDisplayMetrics);
+        return mTmpDisplayMetrics.densityDpi;
+    }
+
+    /**
+     * A task change listener that detects background color change of the topmost stack on our
+     * virtual display and updates the background of the surface view. This background will be shown
+     * when surface view is resized, but the app hasn't drawn its content in new size yet.
+     * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
+     * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
+     * when needing to also bring the host Activity to the foreground at the same time.
+     */
+    private class TaskStackListenerImpl extends TaskStackListener {
+
+        @Override
+        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (!isInitialized()) {
+                return;
+            }
+            if (taskInfo.displayId != getDisplayId()) {
+                return;
+            }
+            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
+            if (stackInfo == null) {
+                return;
+            }
+            // Found the topmost stack on target display. Now check if the topmost task's
+            // description changed.
+            if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this,
+                        taskInfo.taskDescription.getBackgroundColor());
+            }
+        }
+
+        @Override
+        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (!isInitialized() || mListener == null
+                    || taskInfo.displayId != getDisplayId()) {
+                return;
+            }
+
+            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the "move to front" then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mListener.onTaskMovedToFront(taskInfo.taskId);
+            }
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
+            if (mListener == null || !isInitialized()) {
+                return;
+            }
+
+            ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the task creation then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mListener.onTaskCreated(taskId, componentName);
+            }
+        }
+
+        @Override
+        public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (mListener == null || !isInitialized()
+                    || taskInfo.displayId != getDisplayId()) {
+                return;
+            }
+
+            mListener.onTaskRemovalStarted(taskInfo.taskId);
+        }
+
+        private ActivityManager.StackInfo getTopMostStackInfo() throws RemoteException {
+            // Find the topmost task on our virtual display - it will define the background
+            // color of the surface view during resizing.
+            final int displayId = getDisplayId();
+            final List<ActivityManager.StackInfo> stackInfoList =
+                    mActivityTaskManager.getAllStackInfosOnDisplay(displayId);
+            if (stackInfoList.isEmpty()) {
+                return null;
+            }
+            return stackInfoList.get(0);
+        }
+    }
+}
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index fcf4830..b038b0f 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -41,5 +41,12 @@
      * has children. The Divider impl looks at the info and can see that the secondary root task
      * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX.
      */
-    void onTaskInfoChanged(in ActivityManager.RunningTaskInfo info);
+    void onTaskInfoChanged(in ActivityManager.RunningTaskInfo taskInfo);
+
+    /**
+     * Called when the task organizer has requested
+     * {@link ITaskOrganizerController.setInterceptBackPressedOnTaskRoot} to get notified when the
+     * user has pressed back on the root activity of a task controlled by the task organizer.
+     */
+    void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
 }
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index a8b6aae..ba65915 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -57,4 +57,10 @@
      * and thus new tasks just end up directly on the display.
      */
     void setLaunchRoot(int displayId, in IWindowContainer root);
+
+    /**
+     * Requests that the given task organizer is notified when back is pressed on the root activity
+     * of one of its controlled tasks.
+     */
+    void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer, boolean interceptBackPressed);
 }
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 4bd5b29..5590e72 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -137,6 +137,16 @@
             getController().setLaunchRoot(displayId, root);
         }
 
+        /**
+         * Requests that the given task organizer is notified when back is pressed on the root
+         * activity of one of its controlled tasks.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+        public static void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer,
+                boolean interceptBackPressed) throws RemoteException {
+            getController().setInterceptBackPressedOnTaskRoot(organizer, interceptBackPressed);
+        }
+
         private static ITaskOrganizerController getController() {
             return ITaskOrganizerControllerSingleton.get();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
new file mode 100644
index 0000000..0d6d137
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.app.TaskEmbedder;
+import android.app.TaskOrganizerTaskEmbedder;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import dalvik.system.CloseGuard;
+
+
+public class BubbleTaskView extends SurfaceView implements SurfaceHolder.Callback,
+        TaskEmbedder.Host {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleTaskView" : TAG_BUBBLES;
+
+    private final CloseGuard mGuard = CloseGuard.get();
+    private boolean mOpened; // Protected by mGuard.
+
+    private TaskEmbedder mTaskEmbedder;
+    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    private final Rect mTmpRect = new Rect();
+
+    public BubbleTaskView(Context context) {
+        super(context);
+
+        mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
+        setUseAlpha();
+        getHolder().addCallback(this);
+
+        mOpened = true;
+        mGuard.open("release");
+    }
+
+    public void setCallback(TaskEmbedder.Listener callback) {
+        if (callback == null) {
+            mTaskEmbedder.setListener(null);
+            return;
+        }
+        mTaskEmbedder.setListener(callback);
+    }
+
+    public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+            @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
+        mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
+    }
+
+    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options) {
+        mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
+    }
+
+    public void onLocationChanged() {
+        mTaskEmbedder.notifyBoundsChanged();
+    }
+
+    @Override
+    public Rect getScreenBounds() {
+        getBoundsOnScreen(mTmpRect);
+        return mTmpRect;
+    }
+
+    @Override
+    public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
+        setResizeBackgroundColor(bgColor);
+    }
+
+    @Override
+    public Region getTapExcludeRegion() {
+        // Not used
+        return null;
+    }
+
+    @Override
+    public Matrix getScreenToTaskMatrix() {
+        // Not used
+        return null;
+    }
+
+    @Override
+    public IWindow getWindow() {
+        // Not used
+        return null;
+    }
+
+    @Override
+    public Point getPositionInWindow() {
+        // Not used
+        return null;
+    }
+
+    @Override
+    public boolean canReceivePointerEvents() {
+        // Not used
+        return false;
+    }
+
+    public void release() {
+        if (!mTaskEmbedder.isInitialized()) {
+            throw new IllegalStateException(
+                    "Trying to release container that is not initialized.");
+        }
+        performRelease();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mGuard != null) {
+                mGuard.warnIfOpen();
+                performRelease();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void performRelease() {
+        if (!mOpened) {
+            return;
+        }
+        getHolder().removeCallback(this);
+        mTaskEmbedder.release();
+        mTaskEmbedder.setListener(null);
+
+        mGuard.close();
+        mOpened = false;
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        if (!mTaskEmbedder.isInitialized()) {
+            mTaskEmbedder.initialize(getSurfaceControl());
+        } else {
+            mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
+                    getSurfaceControl()).apply();
+        }
+        mTaskEmbedder.start();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        mTaskEmbedder.resizeTask(width, height);
+        mTaskEmbedder.notifyBoundsChanged();
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        mTaskEmbedder.stop();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index da171b2..6a15cb3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -318,6 +318,11 @@
                 null /* updateBoundsCallback */);
     }
 
+    @Override
+    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        // Do nothing
+    }
+
     /**
      * @return {@code true} if the aspect ratio is changed since no other parameters within
      * {@link PictureInPictureParams} would affect the bounds.
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index 0a528a6..a6f6741 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -114,6 +114,10 @@
         mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo));
     }
 
+    @Override
+    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+    }
+
     /**
      * This is effectively a finite state machine which moves between the various split-screen
      * presentations based on the contents of the split regions.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7e999c6..bda6da5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2420,7 +2420,12 @@
                 return;
             }
             ActivityStack stack = r.getRootTask();
-            if (stack != null && stack.isSingleTaskInstance()) {
+            final TaskOrganizerController taskOrgController =
+                    mWindowOrganizerController.mTaskOrganizerController;
+            if (taskOrgController.handleInterceptBackPressedOnTaskRoot(stack)) {
+                // This task is handled by a task organizer that has requested the back pressed
+                // callback
+            } else if (stack != null && (stack.isSingleTaskInstance())) {
                 // Single-task stacks are used for activities which are presented in floating
                 // windows above full screen activities. Instead of directly finishing the
                 // task, a task change listener is used to notify SystemUI so the action can be
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4a7edee..459a8d6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4131,6 +4131,10 @@
                 return true;
             }
 
+            if (task.isOrganized()) {
+                return true;
+            }
+
             // We need to use the task's dim bounds (which is derived from the visible bounds of
             // its apps windows) for any touch-related tests. Can't use the task's original
             // bounds because it might be adjusted to fit the content frame. One example is when
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8edcd2f..7c47e50 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -32,7 +32,6 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.window.ITaskOrganizer;
@@ -46,7 +45,6 @@
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.WeakHashMap;
 
 /**
@@ -88,6 +86,7 @@
         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;
@@ -100,6 +99,10 @@
             mUid = uid;
         }
 
+        void setInterceptBackPressedOnTaskRoot(boolean interceptBackPressed) {
+            mInterceptBackPressedOnTaskRoot = interceptBackPressed;
+        }
+
         void addTask(Task t) {
             mOrganizedTasks.add(t);
             try {
@@ -473,6 +476,41 @@
         }
     }
 
+    @Override
+    public void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer,
+            boolean interceptBackPressed) {
+        enforceStackPermission("setInterceptBackPressedOnTaskRoot()");
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
+                if (state != null) {
+                    state.setInterceptBackPressedOnTaskRoot(interceptBackPressed);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
+        if (task == null || !task.isOrganized()) {
+            return false;
+        }
+
+        final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+        if (!state.mInterceptBackPressedOnTaskRoot) {
+            return false;
+        }
+
+        try {
+            state.mOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending interceptBackPressedOnTaskRoot callback" + e);
+        }
+        return true;
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.print(prefix); pw.println("TaskOrganizerController:");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index ed400ea..bc1f925 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1047,5 +1047,8 @@
                 }
             }
         }
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        }
     };
 }
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 4cc84a6..f05acce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -50,6 +50,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
+import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -420,6 +421,10 @@
             @Override
             public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException {
             }
+
+            @Override
+            public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            }
         };
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener,
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
@@ -474,6 +479,10 @@
                 lastReportedTiles.add(info);
                 called[0] = true;
             }
+
+            @Override
+            public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            }
         };
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener,
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
@@ -531,6 +540,10 @@
             public void onTaskInfoChanged(RunningTaskInfo info) {
                 lastReportedTiles.put(info.token.asBinder(), info);
             }
+
+            @Override
+            public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            }
         };
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
                 listener, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -751,6 +764,9 @@
         @Override
         public void onTaskInfoChanged(RunningTaskInfo info) {
         }
+        @Override
+        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+        }
     };
 
     private ActivityRecord makePipableActivity() {
@@ -827,4 +843,31 @@
         task.removeImmediately();
         verify(organizer).onTaskVanished(any());
     }
+
+    @Test
+    public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
+        final ActivityStack stack = createStack();
+        final Task task = createTask(stack);
+        final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(
+                stack.mDisplayContent, task);
+        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+
+        // Setup the task to be controlled by the MW mode organizer
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        assertTrue(stack.isOrganized());
+
+        // Verify a back pressed does not call the organizer
+        mWm.mAtmService.onBackPressedOnTaskRoot(activity.token,
+                new IRequestFinishCallback.Default());
+        verify(organizer, never()).onBackPressedOnTaskRoot(any());
+
+        // Enable intercepting back
+        mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(organizer,
+                true);
+
+        // Verify now that the back press does call the organizer
+        mWm.mAtmService.onBackPressedOnTaskRoot(activity.token,
+                new IRequestFinishCallback.Default());
+        verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
+    }
 }
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
index 5afd39e..d468076 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
@@ -19,8 +19,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.window.WindowOrganizer.TaskOrganizer;
 
-import android.app.ActivityManager;
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
@@ -35,10 +35,10 @@
 import android.view.SurfaceHolder;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.WindowContainerTransaction;
-import android.widget.LinearLayout;
 import android.window.WindowOrganizer;
 
 public class TaskOrganizerMultiWindowTest extends Activity {
@@ -163,6 +163,9 @@
         @Override
         public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         }
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        }
     }
 
     Organizer mOrganizer = new Organizer();
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
index 520bc25..a589d95 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -25,10 +25,10 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.ViewGroup;
-import android.window.ITaskOrganizer;
-import android.window.WindowContainerTransaction;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
+import android.window.ITaskOrganizer;
+import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
 public class TaskOrganizerPipTest extends Service {
@@ -52,6 +52,9 @@
         }
         public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         }
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        }
     }
 
     Organizer mOrganizer = new Organizer();