Extracts core logic of ActivityView into TaskEmbedder
TaskEmbedder is a new building block for task embedding which
will be used by SystemUI to perform more complex operations
than what can be acheived using ActivityView today.
For task embedding use cases, integrating TaskEmbedder directly
with a SurfaceView (or other surface) will allow:
- management of multiple tasks within a single parent surface
- access to the surfacecontrol of each task for manipulation/animation
- (SurfaceView) configure whether zOrder above or below.
See: go/av-refactor
Change-Id: Ia813c52bc2da3a776e727b5bbd2b03b8ff09f302
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index fbf1f59..79ab67a 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 TaskEmbedder mTaskEmbedder;
+
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 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 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 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 @@
* @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 @@
* 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 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 @@
* @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 @@
* 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);
+ mTaskEmbedder.performBackPress();
}
- 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);
-
- 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;
+ return false;
}
-
- 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);
- }
+ 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 @@
* @see IWindowManager#setForwardedInsets
*/
public void setForwardedInsets(Insets insets) {
- mForwardedInsets = insets;
- if (mVirtualDisplay == null) {
- return;
- }
- try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ mTaskEmbedder.setForwardedInsets(insets);
+ }
+
+ // Host
+
+ /** @hide */
+ @Override
+ public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setResizeBackgroundColor(bgColor);
}
}
- /**
- * 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 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;
+ }
- @Override
- public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
- throws RemoteException {
- if (mVirtualDisplay == null
- || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
- return;
- }
+ /** @hide */
+ @Override
+ public Matrix getScreenToTaskMatrix() {
+ getLocationOnScreen(mTmpArray);
+ mScreenSurfaceMatrix.set(getMatrix());
+ mScreenSurfaceMatrix.postTranslate(mTmpArray[0], mTmpArray[1]);
+ return mScreenSurfaceMatrix;
+ }
- 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 Point getPositionInWindow() {
+ getLocationInWindow(mTmpArray);
+ mWindowPosition.set(mTmpArray[0], mTmpArray[1]);
+ return mWindowPosition;
+ }
+
+ /** @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;
- }
-
- 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);
- }
+ public void onReleased() {
+ mCallback.onActivityViewDestroyed(ActivityView.this);
}
@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 onTaskCreated(int taskId, ComponentName name) {
+ mCallback.onTaskCreated(taskId, name);
}
- 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();
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ mCallback.onTaskMovedToFront(taskId);
+ }
- // 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 0000000..a1389bd
--- /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;
+ }
+ }
+}