diff options
| author | 2019-11-07 18:37:53 +0000 | |
|---|---|---|
| committer | 2019-11-07 18:37:53 +0000 | |
| commit | 0dc6c591bae212bd0e3067c0d1a92a75ceba47e9 (patch) | |
| tree | ae5de3c4f0a2f3a12e927716a0b45d8a361a0b89 | |
| parent | 93bdf9a2d00aaefde495c05296b3d88ed25f3b7a (diff) | |
| parent | 89ac988242d1320f8c459500c9bca49f57eeda74 (diff) | |
Merge "Extracts core logic of ActivityView into TaskEmbedder"
| -rw-r--r-- | core/java/android/app/ActivityView.java | 501 | ||||
| -rw-r--r-- | core/java/android/app/TaskEmbedder.java | 674 |
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; + } + } +} |