summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-11-07 18:37:53 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-11-07 18:37:53 +0000
commit0dc6c591bae212bd0e3067c0d1a92a75ceba47e9 (patch)
treeae5de3c4f0a2f3a12e927716a0b45d8a361a0b89
parent93bdf9a2d00aaefde495c05296b3d88ed25f3b7a (diff)
parent89ac988242d1320f8c459500c9bca49f57eeda74 (diff)
Merge "Extracts core logic of ActivityView into TaskEmbedder"
-rw-r--r--core/java/android/app/ActivityView.java501
-rw-r--r--core/java/android/app/TaskEmbedder.java674
2 files changed, 796 insertions, 379 deletions
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index fbf1f59141a8..79ab67ac148c 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -16,94 +16,58 @@
package android.app;
-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.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.app.ActivityManager.StackInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Matrix;
+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.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.IWindow;
import android.view.IWindowManager;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
-import android.view.SurfaceSession;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.inputmethod.InputMethodManager;
import dalvik.system.CloseGuard;
-import java.util.List;
-
/**
- * Activity container that allows launching activities into itself.
+ * Task container that allows launching activities into itself.
* <p>Activity launching into this container is restricted by the same rules that apply to launching
* on VirtualDisplays.
* @hide
*/
@TestApi
-public class ActivityView extends ViewGroup {
+public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
- private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
private static final String TAG = "ActivityView";
- private VirtualDisplay mVirtualDisplay;
- private final SurfaceView mSurfaceView;
-
- /**
- * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
- * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
- */
- private SurfaceControl mRootSurfaceControl;
+ private TaskEmbedder mTaskEmbedder;
+ private final SurfaceView mSurfaceView;
private final SurfaceCallback mSurfaceCallback;
- private StateCallback mActivityViewCallback;
-
- private IActivityTaskManager mActivityTaskManager;
- // Temp container to store view coordinates in window.
- private final int[] mLocationInWindow = new int[2];
-
- // The latest tap exclude region that we've sent to WM.
- private final Region mTapExcludeRegion = new Region();
-
- private TaskStackListener mTaskStackListener;
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
- /** The ActivityView is only allowed to contain one task. */
- private final boolean mSingleTaskInstance;
-
- private Insets mForwardedInsets;
-
- private float mCornerRadius;
+ // For Host
+ private final Point mWindowPosition = new Point();
+ private final int[] mTmpArray = new int[2];
+ private final Matrix mScreenSurfaceMatrix = new Matrix();
+ private final Region mTapExcludeRegion = new Region();
public ActivityView(Context context) {
this(context, null /* attrs */);
@@ -120,9 +84,7 @@ public class ActivityView extends ViewGroup {
public ActivityView(
Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
super(context, attrs, defStyle);
- mSingleTaskInstance = singleTaskInstance;
-
- mActivityTaskManager = ActivityTaskManager.getService();
+ mTaskEmbedder = new TaskEmbedder(getContext(), 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.
@@ -189,11 +151,11 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(Intent)
*/
public void setCallback(StateCallback callback) {
- mActivityViewCallback = callback;
-
- if (mVirtualDisplay != null && mActivityViewCallback != null) {
- mActivityViewCallback.onActivityViewReady(this);
+ if (callback == null) {
+ mTaskEmbedder.setListener(null);
+ return;
}
+ mTaskEmbedder.setListener(new StateCallbackAdapter(callback));
}
/**
@@ -262,8 +224,7 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(PendingIntent)
*/
public void startActivity(@NonNull Intent intent) {
- final ActivityOptions options = prepareActivityOptions();
- getContext().startActivity(intent, options.toBundle());
+ mTaskEmbedder.startActivity(intent);
}
/**
@@ -284,8 +245,7 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(PendingIntent)
*/
public void startActivity(@NonNull Intent intent, UserHandle user) {
- final ActivityOptions options = prepareActivityOptions();
- getContext().startActivityAsUser(intent, options.toBundle(), user);
+ mTaskEmbedder.startActivity(intent, user);
}
/**
@@ -304,14 +264,7 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(Intent)
*/
public void startActivity(@NonNull PendingIntent pendingIntent) {
- final ActivityOptions options = prepareActivityOptions();
- try {
- pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
- null /* onFinished */, null /* handler */, null /* requiredPermission */,
- options.toBundle());
- } catch (PendingIntent.CanceledException e) {
- throw new RuntimeException(e);
- }
+ mTaskEmbedder.startActivity(pendingIntent);
}
/**
@@ -333,28 +286,7 @@ public class ActivityView extends ViewGroup {
*/
public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options) {
- options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
- try {
- pendingIntent.send(getContext(), 0 /* code */, fillInIntent,
- null /* onFinished */, null /* handler */, null /* requiredPermission */,
- options.toBundle());
- } catch (PendingIntent.CanceledException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Check if container is ready to launch and create {@link ActivityOptions} to target the
- * virtual display.
- */
- private ActivityOptions prepareActivityOptions() {
- if (mVirtualDisplay == null) {
- throw new IllegalStateException(
- "Trying to start activity before ActivityView is ready.");
- }
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
- return options;
+ mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
}
/**
@@ -366,7 +298,7 @@ public class ActivityView extends ViewGroup {
* @see StateCallback
*/
public void release() {
- if (mVirtualDisplay == null) {
+ if (!mTaskEmbedder.isInitialized()) {
throw new IllegalStateException(
"Trying to release container that is not initialized.");
}
@@ -378,15 +310,7 @@ public class ActivityView extends ViewGroup {
* regions and avoid focus switches by touches on this view.
*/
public void onLocationChanged() {
- updateLocationAndTapExcludeRegion();
- }
-
- private void clearActivityViewGeometryForIme() {
- if (mVirtualDisplay == null) {
- return;
- }
- final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
- mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
+ mTaskEmbedder.notifyBoundsChanged();
}
@Override
@@ -419,102 +343,31 @@ public class ActivityView extends ViewGroup {
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.
- updateLocationAndTapExcludeRegion();
+ mTaskEmbedder.notifyBoundsChanged();
return super.gatherTransparentRegion(region);
}
- /**
- * Sends current location in window and tap exclude region to WM for this view.
- */
- private void updateLocationAndTapExcludeRegion() {
- if (mVirtualDisplay == null || !isAttachedToWindow()) {
- return;
- }
- try {
- int x = mLocationInWindow[0];
- int y = mLocationInWindow[1];
- getLocationInWindow(mLocationInWindow);
- if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
- x = mLocationInWindow[0];
- y = mLocationInWindow[1];
- final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
- WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
- getWindow(), x, y, displayId);
-
- // Also report this geometry information to InputMethodManagerService.
- // TODO(b/115693908): Unify this logic into the above WMS-based one.
- // TODO(b/138175283): Address the location update when the host of this view is
- // moving.
- final Matrix matrix = new Matrix();
- final int[] locationOnScreen = new int[2];
- getLocationOnScreen(locationOnScreen);
- final int dx = locationOnScreen[0];
- final int dy = locationOnScreen[1];
- matrix.set(getMatrix());
- matrix.postTranslate(dx, dy);
- mContext.getSystemService(InputMethodManager.class)
- .reportActivityView(displayId, matrix);
- }
- updateTapExcludeRegion(x, y);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
- }
-
- /** Computes and sends current tap exclude region to WM for this view. */
- private void updateTapExcludeRegion(int x, int y) throws RemoteException {
- if (!canReceivePointerEvents()) {
- cleanTapExcludeRegion();
- return;
- }
- mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
-
- // There might be views on top of us. We need to subtract those areas from the tap
- // exclude region.
- final ViewParent parent = getParent();
- if (parent != null) {
- parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
- }
-
- WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
- mTapExcludeRegion);
- }
-
private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
- if (mVirtualDisplay == null) {
- initVirtualDisplay(new SurfaceSession());
- if (mVirtualDisplay != null && mActivityViewCallback != null) {
- mActivityViewCallback.onActivityViewReady(ActivityView.this);
- }
+ if (!mTaskEmbedder.isInitialized()) {
+ initTaskEmbedder(mSurfaceView.getSurfaceControl());
} else {
- mTmpTransaction.reparent(mRootSurfaceControl,
+ mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
mSurfaceView.getSurfaceControl()).apply();
}
-
- if (mVirtualDisplay != null) {
- mVirtualDisplay.setDisplayState(true);
- }
-
- updateLocationAndTapExcludeRegion();
+ mTaskEmbedder.start();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
- if (mVirtualDisplay != null) {
- mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
- }
- updateLocationAndTapExcludeRegion();
+ mTaskEmbedder.resizeTask(width, height);
+ mTaskEmbedder.notifyBoundsChanged();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
- if (mVirtualDisplay != null) {
- mVirtualDisplay.setDisplayState(false);
- }
- clearActivityViewGeometryForIme();
- cleanTapExcludeRegion();
+ mTaskEmbedder.stop();
}
}
@@ -528,10 +381,7 @@ public class ActivityView extends ViewGroup {
* @return the display id of the virtual display.
*/
public int getVirtualDisplayId() {
- if (mVirtualDisplay != null) {
- return mVirtualDisplay.getDisplay().getDisplayId();
- }
- return INVALID_DISPLAY;
+ return mTaskEmbedder.getDisplayId();
}
/**
@@ -539,135 +389,36 @@ public class ActivityView extends ViewGroup {
* 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;
- }
-
- private void initVirtualDisplay(SurfaceSession surfaceSession) {
- if (mVirtualDisplay != null) {
- throw new IllegalStateException("Trying to initialize for the second time.");
- }
-
- final int width = mSurfaceView.getWidth();
- final int height = mSurfaceView.getHeight();
- final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ mTaskEmbedder.performBackPress();
+ }
- mVirtualDisplay = displayManager.createVirtualDisplay(
- DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
- getBaseDisplayDensity(), null,
- VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
- if (mVirtualDisplay == null) {
+ /**
+ * Initializes the task embedder.
+ *
+ * @param parent control for the surface to parent to
+ * @return true if the task embedder has been initialized
+ */
+ private boolean initTaskEmbedder(SurfaceControl parent) {
+ if (!mTaskEmbedder.initialize(parent)) {
Log.e(TAG, "Failed to initialize ActivityView");
- return;
- }
-
- final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-
- mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
- .setContainerLayer()
- .setParent(mSurfaceView.getSurfaceControl())
- .setName(DISPLAY_NAME)
- .build();
-
- try {
- // TODO: Find a way to consolidate these calls to the server.
- WindowManagerGlobal.getWindowSession().reparentDisplayContent(
- getWindow(), mRootSurfaceControl, displayId);
- wm.dontOverrideDisplayInfo(displayId);
- if (mSingleTaskInstance) {
- mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
- }
- wm.setForwardedInsets(displayId, mForwardedInsets);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
-
- mTmpTransaction.show(mRootSurfaceControl).apply();
- mTaskStackListener = new TaskStackListenerImpl();
- try {
- mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register task stack listener", e);
+ return false;
}
+ mTmpTransaction.show(mTaskEmbedder.getSurfaceControl()).apply();
+ return true;
}
private void performRelease() {
if (!mOpened) {
return;
}
-
mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
-
- cleanTapExcludeRegion();
-
- if (mTaskStackListener != null) {
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to unregister task stack listener", e);
- }
- mTaskStackListener = null;
- }
-
- final boolean displayReleased;
- if (mVirtualDisplay != null) {
- mVirtualDisplay.release();
- mVirtualDisplay = null;
- displayReleased = true;
- } else {
- displayReleased = false;
- }
-
- if (displayReleased && mActivityViewCallback != null) {
- mActivityViewCallback.onActivityViewDestroyed(this);
- }
+ mTaskEmbedder.setListener(null);
+ mTaskEmbedder.release();
mGuard.close();
mOpened = false;
}
- /** Report to server that tap exclude region on hosting display should be cleared. */
- private void cleanTapExcludeRegion() {
- if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
- return;
- }
- // Update tap exclude region with a null region to clean the state on server.
- try {
- WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
- null /* region */);
- mTapExcludeRegion.setEmpty();
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
- }
-
- /** Get density of the hosting display. */
- private int getBaseDisplayDensity() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- final DisplayMetrics metrics = new DisplayMetrics();
- wm.getDefaultDisplay().getMetrics(metrics);
- return metrics.densityDpi;
- }
-
@Override
protected void finalize() throws Throwable {
try {
@@ -686,108 +437,100 @@ public class ActivityView extends ViewGroup {
* @see IWindowManager#setForwardedInsets
*/
public void setForwardedInsets(Insets insets) {
- mForwardedInsets = insets;
- if (mVirtualDisplay == null) {
- return;
+ mTaskEmbedder.setForwardedInsets(insets);
+ }
+
+ // Host
+
+ /** @hide */
+ @Override
+ public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setResizeBackgroundColor(bgColor);
}
- try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ }
+
+ /** @hide */
+ @Override
+ public Region getTapExcludeRegion() {
+ if (isAttachedToWindow() && canReceivePointerEvents()) {
+ Point windowPos = getPositionInWindow();
+ mTapExcludeRegion.set(
+ windowPos.x,
+ windowPos.y,
+ windowPos.x + getWidth(),
+ windowPos.y + getHeight());
+ // There might be views on top of us. We need to subtract those areas from the tap
+ // exclude region.
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
+ }
+ } else {
+ mTapExcludeRegion.setEmpty();
}
+ return mTapExcludeRegion;
}
- /**
- * 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 {
+ /** @hide */
+ @Override
+ public Matrix getScreenToTaskMatrix() {
+ getLocationOnScreen(mTmpArray);
+ mScreenSurfaceMatrix.set(getMatrix());
+ mScreenSurfaceMatrix.postTranslate(mTmpArray[0], mTmpArray[1]);
+ return mScreenSurfaceMatrix;
+ }
- @Override
- public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
- throws RemoteException {
- if (mVirtualDisplay == null
- || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
- return;
- }
+ /** @hide */
+ @Override
+ public Point getPositionInWindow() {
+ getLocationInWindow(mTmpArray);
+ mWindowPosition.set(mTmpArray[0], mTmpArray[1]);
+ return mWindowPosition;
+ }
- 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]) {
- mSurfaceView.setResizeBackgroundColor(
- taskInfo.taskDescription.getBackgroundColor());
- }
+ /** @hide */
+ @Override
+ public IWindow getWindow() {
+ return super.getWindow();
+ }
+
+ /** @hide */
+ @Override
+ public boolean canReceivePointerEvents() {
+ return super.canReceivePointerEvents();
+ }
+
+ private final class StateCallbackAdapter implements TaskEmbedder.Listener {
+ private final StateCallback mCallback;
+
+ private StateCallbackAdapter(ActivityView.StateCallback cb) {
+ mCallback = cb;
}
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
- throws RemoteException {
- if (mActivityViewCallback == null || mVirtualDisplay == null
- || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
- return;
- }
-
- 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]) {
- mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
- }
+ public void onInitialized() {
+ mCallback.onActivityViewReady(ActivityView.this);
}
@Override
- public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
- if (mActivityViewCallback == null || mVirtualDisplay == null) {
- return;
- }
+ public void onReleased() {
+ mCallback.onActivityViewDestroyed(ActivityView.this);
+ }
- 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]) {
- mActivityViewCallback.onTaskCreated(taskId, componentName);
- }
+ @Override
+ public void onTaskCreated(int taskId, ComponentName name) {
+ mCallback.onTaskCreated(taskId, name);
}
@Override
- public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
- throws RemoteException {
- if (mActivityViewCallback == null || mVirtualDisplay == null
- || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
- return;
- }
- mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
+ public void onTaskMovedToFront(int taskId) {
+ mCallback.onTaskMovedToFront(taskId);
}
- private 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 = mVirtualDisplay.getDisplay().getDisplayId();
- final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
-
- // Iterate through stacks from top to bottom.
- final int stackCount = stackInfoList.size();
- for (int i = 0; i < stackCount; i++) {
- final StackInfo stackInfo = stackInfoList.get(i);
- // Only look for stacks on our virtual display.
- if (stackInfo.displayId != displayId) {
- continue;
- }
- // Found the topmost stack on target display.
- return stackInfo;
- }
- return null;
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ mCallback.onTaskRemovalStarted(taskId);
}
}
}
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/app/TaskEmbedder.java
new file mode 100644
index 000000000000..a1389bdbaf7b
--- /dev/null
+++ b/core/java/android/app/TaskEmbedder.java
@@ -0,0 +1,674 @@
+/*
+ * 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.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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+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.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.WindowManager;
+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 {
+ private static final String TAG = "TaskEmbedder";
+ private static final String DISPLAY_NAME = "TaskVirtualDisplay";
+
+ /**
+ * A component which will host the task.
+ */
+ public interface Host {
+ /** @return the screen area where touches should be dispatched to the embedded Task */
+ Region getTapExcludeRegion();
+
+ /** @return a matrix which transforms from screen-space to the embedded task surface */
+ Matrix getScreenToTaskMatrix();
+
+ /** @return the window containing the parent surface, if attached and available */
+ @Nullable IWindow getWindow();
+
+ /** @return the x/y offset from the origin of the window to the surface */
+ Point getPositionInWindow();
+
+ /** @return whether this surface is able to receive pointer events */
+ boolean canReceivePointerEvents();
+
+ /** @return the width of the container for the embedded task */
+ int getWidth();
+
+ /** @return the height of the container for the embedded task */
+ int getHeight();
+
+ /**
+ * Called to inform the host of the task's background color. This can be used to
+ * fill unpainted areas if necessary.
+ */
+ void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor);
+ }
+
+ /**
+ * Describes changes to the state of the TaskEmbedder as well the tasks within.
+ */
+ public interface Listener {
+ /** Called when the container is ready for launching activities. */
+ default void onInitialized() {}
+
+ /** Called when the container can no longer launch activities. */
+ default void onReleased() {}
+
+ /** Called when a task is created inside the container. */
+ default void onTaskCreated(int taskId, ComponentName name) {}
+
+ /** 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) {}
+ }
+
+ private 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 final CloseGuard mGuard = CloseGuard.get();
+
+
+ /**
+ * 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
+ */
+ public TaskEmbedder(Context context, TaskEmbedder.Host host, boolean singleTaskInstance) {
+ 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.
+ *
+ * @param parent the surface control for the parent surface
+ * @return true if initialized successfully
+ */
+ public boolean initialize(SurfaceControl parent) {
+ if (mVirtualDisplay != null) {
+ 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 ActivityDisplay will be reparented
+ final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
+ mSurfaceControl = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setParent(parent)
+ .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 (mListener != null && mVirtualDisplay != null) {
+ mListener.onInitialized();
+ }
+ mOpened = true;
+ mGuard.open("release");
+ return true;
+ }
+
+ /**
+ * Returns the surface control for the task surface. This should be parented to a screen
+ * surface for display/embedding purposes.
+ *
+ * @return the surface control for the task
+ */
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ /**
+ * Set forwarded insets on the virtual display.
+ *
+ * @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;
+ }
+
+ /**
+ * Set the callback to be notified about state changes.
+ * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
+ * <p>Note: If the instance was ready prior to this call being made, then
+ * {@link Listener#onInitialized()} will be called from within this method call.
+ *
+ * @param listener The listener to report events to.
+ *
+ * @see ActivityView.StateCallback
+ * @see #startActivity(Intent)
+ */
+ void setListener(TaskEmbedder.Listener listener) {
+ mListener = listener;
+ if (mListener != null && isInitialized()) {
+ mListener.onInitialized();
+ }
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param intent Intent used to launch an activity
+ *
+ * @see #startActivity(PendingIntent)
+ */
+ public void startActivity(@NonNull Intent intent) {
+ final ActivityOptions options = prepareActivityOptions();
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param intent Intent used to launch an activity
+ * @param user The UserHandle of the user to start this activity for
+ *
+ * @see #startActivity(PendingIntent)
+ */
+ public void startActivity(@NonNull Intent intent, UserHandle user) {
+ final ActivityOptions options = prepareActivityOptions();
+ mContext.startActivityAsUser(intent, options.toBundle(), user);
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param pendingIntent Intent used to launch an activity
+ *
+ * @see #startActivity(Intent)
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent) {
+ final ActivityOptions options = prepareActivityOptions();
+ try {
+ pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (PendingIntent.CanceledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param pendingIntent Intent used to launch an activity
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity
+ *
+ * @see #startActivity(Intent)
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options) {
+
+ options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
+ try {
+ pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (PendingIntent.CanceledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Check if container is ready to launch and create {@link ActivityOptions} to target the
+ * virtual display.
+ */
+ private ActivityOptions prepareActivityOptions() {
+ if (mVirtualDisplay == null) {
+ throw new IllegalStateException(
+ "Trying to start activity before ActivityView is ready.");
+ }
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(getDisplayId());
+ 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(), hashCode(), 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, int, Region)
+ */
+ private void applyTapExcludeRegion(IWindow window, int regionId,
+ @Nullable Region tapExcludeRegion) {
+ try {
+ IWindowSession session = WindowManagerGlobal.getWindowSession();
+ session.updateTapExcludeRegion(window, regionId, 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(), hashCode(), 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.
+ *
+ * <p>Note: Calling this method is allowed after {@link Listener#onInitialized()} callback is
+ * triggered and before {@link Listener#onReleased()}.
+ */
+ public void release() {
+ if (mVirtualDisplay == null) {
+ throw new IllegalStateException(
+ "Trying to release container that is not initialized.");
+ }
+ performRelease();
+ }
+
+ private boolean performRelease() {
+ 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();
+
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister task stack listener", e);
+ }
+ mTaskStackListener = null;
+ }
+
+ boolean reportReleased = false;
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ reportReleased = true;
+
+ }
+
+ if (mListener != null && reportReleased) {
+ mListener.onReleased();
+ }
+ mOpened = false;
+ mGuard.close();
+ return true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ performRelease();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /** Get density of the hosting display. */
+ private int getBaseDisplayDensity() {
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ final DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metrics);
+ return metrics.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.getAllStackInfos();
+
+ // Iterate through stacks from top to bottom.
+ final int stackCount = stackInfoList.size();
+ for (int i = 0; i < stackCount; i++) {
+ final ActivityManager.StackInfo stackInfo = stackInfoList.get(i);
+ // Only look for stacks on our virtual display.
+ if (stackInfo.displayId != displayId) {
+ continue;
+ }
+ // Found the topmost stack on target display.
+ return stackInfo;
+ }
+ return null;
+ }
+ }
+}