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();