summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java132
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewContainer.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainer.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt212
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt90
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainerTest.kt68
13 files changed, 673 insertions, 107 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index d4524282afa2..eeb3662dd45a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -439,7 +439,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
if (!decoration.isHandleMenuActive()) {
moveTaskToFront(decoration.mTaskInfo);
- decoration.createHandleMenu();
+ decoration.createHandleMenu(mSplitScreenController);
} else {
decoration.closeHandleMenu();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 644fd4ba5a54..d822dfd61eb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -68,6 +68,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -650,7 +651,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
/**
* Create and display handle menu window.
*/
- void createHandleMenu() {
+ void createHandleMenu(SplitScreenController splitScreenController) {
loadAppInfoIfNeeded();
mHandleMenu = new HandleMenu.Builder(this)
.setAppIcon(mAppIconBitmap)
@@ -660,6 +661,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setLayoutId(mRelayoutParams.mLayoutResId)
.setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
.setCaptionHeight(mResult.mCaptionHeight)
+ .setDisplayController(mDisplayController)
+ .setSplitScreenController(splitScreenController)
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
@@ -815,11 +818,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// We want handle to remain pressed if the pointer moves outside of it during a drag.
handle.setPressed((inHandle && action == ACTION_DOWN)
|| (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
- if (isHandleMenuActive()) {
+ if (isHandleMenuActive() && !isMenuAboveStatusBar()) {
mHandleMenu.checkMotionEvent(ev);
}
}
+ private boolean isMenuAboveStatusBar() {
+ return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
+ }
+
private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
@@ -868,6 +875,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return exclusionRegion;
}
+ int getCaptionX() {
+ return mResult.mCaptionX;
+ }
+
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
return getCaptionHeightIdStatic(windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index c22b621f2111..bfc4e0dbb8b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -23,6 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
@@ -34,6 +36,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -42,7 +45,15 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.window.SurfaceSyncGroup;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
/**
* Handle menu opened when the appropriate button is clicked on.
@@ -56,15 +67,19 @@ class HandleMenu {
private static final String TAG = "HandleMenu";
private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
private final Context mContext;
- private final WindowDecoration mParentDecor;
- private WindowDecoration.AdditionalWindow mHandleMenuWindow;
- private final PointF mHandleMenuPosition = new PointF();
+ private final DesktopModeWindowDecoration mParentDecor;
+ @VisibleForTesting
+ AdditionalViewContainer mHandleMenuViewContainer;
+ @VisibleForTesting
+ final PointF mHandleMenuPosition = new PointF();
private final boolean mShouldShowWindowingPill;
private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
private final View.OnTouchListener mOnTouchListener;
private final RunningTaskInfo mTaskInfo;
+ private final DisplayController mDisplayController;
+ private final SplitScreenController mSplitScreenController;
private final int mLayoutResId;
private int mMarginMenuTop;
private int mMarginMenuStart;
@@ -74,12 +89,16 @@ class HandleMenu {
private HandleMenuAnimator mHandleMenuAnimator;
- HandleMenu(WindowDecoration parentDecor, int layoutResId, View.OnClickListener onClickListener,
- View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName,
- boolean shouldShowWindowingPill, int captionHeight) {
+ HandleMenu(DesktopModeWindowDecoration parentDecor, int layoutResId,
+ View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
+ Bitmap appIcon, CharSequence appName, DisplayController displayController,
+ SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
+ int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
+ mDisplayController = displayController;
+ mSplitScreenController = splitScreenController;
mLayoutResId = layoutResId;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
@@ -95,20 +114,27 @@ class HandleMenu {
final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- createHandleMenuWindow(t, ssg);
+ createHandleMenuViewContainer(t, ssg);
ssg.addTransaction(t);
ssg.markSyncReady();
setupHandleMenu();
animateHandleMenu();
}
- private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ private void createHandleMenuViewContainer(SurfaceControl.Transaction t,
+ SurfaceSyncGroup ssg) {
final int x = (int) mHandleMenuPosition.x;
final int y = (int) mHandleMenuPosition.y;
- mHandleMenuWindow = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, mMenuWidth, mMenuHeight);
- final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+ if (!mTaskInfo.isFreeform() && Flags.enableAdditionalWindowsAboveStatusBar()) {
+ mHandleMenuViewContainer = new AdditionalSystemViewContainer(mContext,
+ R.layout.desktop_mode_window_decor_handle_menu, mTaskInfo.taskId,
+ x, y, mMenuWidth, mMenuHeight);
+ } else {
+ mHandleMenuViewContainer = mParentDecor.addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+ t, ssg, x, y, mMenuWidth, mMenuHeight);
+ }
+ final View handleMenuView = mHandleMenuViewContainer.getView();
mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
}
@@ -129,7 +155,7 @@ class HandleMenu {
* pill.
*/
private void setupHandleMenu() {
- final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ final View handleMenu = mHandleMenuViewContainer.getView();
handleMenu.setOnTouchListener(mOnTouchListener);
setupAppInfoPill(handleMenu);
if (mShouldShowWindowingPill) {
@@ -147,6 +173,7 @@ class HandleMenu {
final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
final TextView appName = handleMenu.findViewById(R.id.application_name);
collapseBtn.setOnClickListener(mOnClickListener);
+ collapseBtn.setTaskInfo(mTaskInfo);
appIcon.setImageBitmap(mAppIconBitmap);
appName.setText(mAppName);
}
@@ -215,32 +242,55 @@ class HandleMenu {
* Updates handle menu's position variables to reflect its next position.
*/
private void updateHandleMenuPillPositions() {
- final int menuX, menuY;
- final int captionWidth = mTaskInfo.getConfiguration()
- .windowConfiguration.getBounds().width();
+ int menuX;
+ final int menuY;
if (mLayoutResId == R.layout.desktop_mode_app_header) {
- // Align the handle menu to the left of the caption.
+ // Align the handle menu to the left side of the caption.
menuX = mMarginMenuStart;
menuY = mMarginMenuTop;
} else {
- // Position the handle menu at the center of the caption.
- menuX = (captionWidth / 2) - (mMenuWidth / 2);
- menuY = mMarginMenuStart;
+ final int handleWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_fullscreen_decor_caption_width);
+ final int handleOffset = (mMenuWidth / 2) - (handleWidth / 2);
+ final int captionX = mParentDecor.getCaptionX();
+ // TODO(b/343561161): This needs to be calculated differently if the task is in
+ // top/bottom split.
+ if (Flags.enableAdditionalWindowsAboveStatusBar()) {
+ final Rect leftOrTopStageBounds = new Rect();
+ if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+ == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ mSplitScreenController.getStageBounds(leftOrTopStageBounds, new Rect());
+ }
+ // In a focused decor, we use global coordinates for handle menu. Therefore we
+ // need to account for other factors like split stage and menu/handle width to
+ // center the menu.
+ final DisplayLayout layout = mDisplayController
+ .getDisplayLayout(mTaskInfo.displayId);
+ menuX = captionX + handleOffset - (layout.width() / 2);
+ if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+ == SPLIT_POSITION_BOTTOM_OR_RIGHT && layout.isLandscape()) {
+ // If this task in the right stage, we need to offset by left stage's width
+ menuX += leftOrTopStageBounds.width();
+ }
+ menuY = mMarginMenuStart - ((layout.height() - mMenuHeight) / 2);
+ } else {
+ final int captionWidth = mTaskInfo.getConfiguration()
+ .windowConfiguration.getBounds().width();
+ menuX = (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mMarginMenuTop;
+ }
}
-
// Handle Menu position setup.
mHandleMenuPosition.set(menuX, menuY);
-
}
/**
* Update pill layout, in case task changes have caused positioning to change.
*/
void relayout(SurfaceControl.Transaction t) {
- if (mHandleMenuWindow != null) {
+ if (mHandleMenuViewContainer != null) {
updateHandleMenuPillPositions();
- t.setPosition(mHandleMenuWindow.mWindowSurface,
- mHandleMenuPosition.x, mHandleMenuPosition.y);
+ mHandleMenuViewContainer.setPosition(t, mHandleMenuPosition.x, mHandleMenuPosition.y);
}
}
@@ -252,7 +302,7 @@ class HandleMenu {
* @param ev the MotionEvent to compare against.
*/
void checkMotionEvent(MotionEvent ev) {
- final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ final View handleMenu = mHandleMenuViewContainer.getView();
final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
final PointF inputPoint = translateInputToLocalSpace(ev);
final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y);
@@ -280,7 +330,7 @@ class HandleMenu {
boolean isValidMenuInput(PointF inputPoint) {
if (!viewsLaidOut()) return true;
return pointInView(
- mHandleMenuWindow.mWindowViewHost.getView(),
+ mHandleMenuViewContainer.getView(),
inputPoint.x - mHandleMenuPosition.x,
inputPoint.y - mHandleMenuPosition.y);
}
@@ -294,7 +344,7 @@ class HandleMenu {
* Check if the views for handle menu can be seen.
*/
private boolean viewsLaidOut() {
- return mHandleMenuWindow.mWindowViewHost.getView().isLaidOut();
+ return mHandleMenuViewContainer.getView().isLaidOut();
}
private void loadHandleMenuDimensions() {
@@ -333,8 +383,8 @@ class HandleMenu {
void close() {
final Runnable after = () -> {
- mHandleMenuWindow.releaseView();
- mHandleMenuWindow = null;
+ mHandleMenuViewContainer.releaseView();
+ mHandleMenuViewContainer = null;
};
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
|| mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
@@ -345,7 +395,7 @@ class HandleMenu {
}
static final class Builder {
- private final WindowDecoration mParent;
+ private final DesktopModeWindowDecoration mParent;
private CharSequence mName;
private Bitmap mAppIcon;
private View.OnClickListener mOnClickListener;
@@ -353,9 +403,10 @@ class HandleMenu {
private int mLayoutId;
private boolean mShowWindowingPill;
private int mCaptionHeight;
+ private DisplayController mDisplayController;
+ private SplitScreenController mSplitScreenController;
-
- Builder(@NonNull WindowDecoration parent) {
+ Builder(@NonNull DesktopModeWindowDecoration parent) {
mParent = parent;
}
@@ -394,9 +445,20 @@ class HandleMenu {
return this;
}
+ Builder setDisplayController(DisplayController displayController) {
+ mDisplayController = displayController;
+ return this;
+ }
+
+ Builder setSplitScreenController(SplitScreenController splitScreenController) {
+ mSplitScreenController = splitScreenController;
+ return this;
+ }
+
HandleMenu build() {
- return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener,
- mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
+ return new HandleMenu(mParent, mLayoutId, mOnClickListener,
+ mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
+ mShowWindowingPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
index 7898567b70e9..18757ef6ff40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
@@ -15,6 +15,10 @@
*/
package com.android.wm.shell.windowdecor
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
@@ -25,10 +29,20 @@ import android.widget.ImageButton
* This is due to the hover events being handled by [DesktopModeWindowDecorViewModel]
* in order to take the status bar layer into account. Handling it in both classes results in a
* flicker when the hover moves from outside to inside status bar layer.
+ * TODO(b/342229481): Remove this and all uses of it once [AdditionalSystemViewContainer] is no longer
+ * guarded by a flag.
*/
-class HandleMenuImageButton(context: Context?, attrs: AttributeSet?) :
- ImageButton(context, attrs) {
+class HandleMenuImageButton(
+ context: Context?,
+ attrs: AttributeSet?
+) : ImageButton(context, attrs) {
+ lateinit var taskInfo: RunningTaskInfo
+
override fun onHoverEvent(motionEvent: MotionEvent): Boolean {
- return false
+ if (Flags.enableAdditionalWindowsAboveStatusBar() || taskInfo.isFreeform) {
+ return super.onHoverEvent(motionEvent)
+ } else {
+ return false
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 22f0adc42f5d..c903d3b2a858 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -50,7 +50,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
import java.util.function.Supplier
@@ -70,7 +70,7 @@ class MaximizeMenu(
private val menuPosition: PointF,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
) {
- private var maximizeMenu: AdditionalWindow? = null
+ private var maximizeMenu: AdditionalViewHostViewContainer? = null
private lateinit var viewHost: SurfaceControlViewHost
private lateinit var leash: SurfaceControl
private val openMenuAnimatorSet = AnimatorSet()
@@ -145,7 +145,8 @@ class MaximizeMenu(
.setPosition(leash, menuPosition.x, menuPosition.y)
.setCornerRadius(leash, cornerRadius)
.show(leash)
- maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
+ maximizeMenu =
+ AdditionalViewHostViewContainer(leash, viewHost, transactionSupplier)
syncQueue.runInSync { transaction ->
transaction.merge(t)
@@ -154,8 +155,7 @@ class MaximizeMenu(
}
private fun animateOpenMenu() {
- val viewHost = maximizeMenu?.mWindowViewHost
- val maximizeMenuView = viewHost?.view ?: return
+ val maximizeMenuView = maximizeMenu?.view ?: return
val maximizeWindowText = maximizeMenuView.requireViewById<TextView>(
R.id.maximize_menu_maximize_window_text)
val snapWindowText = maximizeMenuView.requireViewById<TextView>(
@@ -233,7 +233,7 @@ class MaximizeMenu(
}
private fun setupMaximizeMenu() {
- val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
+ val maximizeMenuView = maximizeMenu?.view ?: return
maximizeMenuView.setOnGenericMotionListener(onGenericMotionListener)
maximizeMenuView.setOnTouchListener(onTouchListener)
@@ -275,7 +275,7 @@ class MaximizeMenu(
* Check if the views for maximize menu can be seen.
*/
private fun viewsLaidOut(): Boolean {
- return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
+ return maximizeMenu?.view?.isLaidOut ?: false
}
fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 2ae3cb9ef3c0..a08f97cde06f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -22,6 +22,7 @@ import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,6 +57,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -402,7 +404,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
- WindowManager.LayoutParams.TYPE_APPLICATION,
+ TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -569,10 +571,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
- * @return the {@link AdditionalWindow} that was added.
+ * @return the {@link AdditionalViewHostViewContainer} that was added.
*/
- AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
- SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height) {
+ AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
+ int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -586,9 +589,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setWindowCrop(windowSurfaceControl, width, height)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(width, height,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
@@ -596,7 +599,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
.create(mDecorWindowContext, mDisplay, windowManager);
ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
- return new AdditionalWindow(windowSurfaceControl, viewHost,
+ return new AdditionalViewHostViewContainer(windowSurfaceControl, viewHost,
mSurfaceControlTransactionSupplier);
}
@@ -739,41 +742,4 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
}
}
-
- /**
- * Subclass for additional windows associated with this WindowDecoration
- */
- static class AdditionalWindow {
- SurfaceControl mWindowSurface;
- SurfaceControlViewHost mWindowViewHost;
- Supplier<SurfaceControl.Transaction> mTransactionSupplier;
-
- AdditionalWindow(SurfaceControl surfaceControl,
- SurfaceControlViewHost surfaceControlViewHost,
- Supplier<SurfaceControl.Transaction> transactionSupplier) {
- mWindowSurface = surfaceControl;
- mWindowViewHost = surfaceControlViewHost;
- mTransactionSupplier = transactionSupplier;
- }
-
- void releaseView() {
- WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
-
- if (mWindowViewHost != null) {
- mWindowViewHost.release();
- mWindowViewHost = null;
- }
- windowManager = null;
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
- boolean released = false;
- if (mWindowSurface != null) {
- t.remove(mWindowSurface);
- mWindowSurface = null;
- released = true;
- }
- if (released) {
- t.apply();
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
new file mode 100644
index 000000000000..6c2c8fd46bc9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.additionalviewcontainer
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+
+/**
+ * An [AdditionalViewContainer] that uses the system [WindowManager] instance. Intended
+ * for view containers that should be above the status bar layer.
+ */
+class AdditionalSystemViewContainer(
+ private val context: Context,
+ layoutId: Int,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int
+) : AdditionalViewContainer() {
+ override val view: View
+
+ init {
+ view = LayoutInflater.from(context).inflate(layoutId, null)
+ val lp = WindowManager.LayoutParams(
+ width, height, x, y,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT
+ )
+ lp.title = "Additional view container of Task=$taskId"
+ lp.setTrustedOverlay()
+ val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
+ wm?.addView(view, lp)
+ }
+
+ override fun releaseView() {
+ context.getSystemService(WindowManager::class.java)?.removeViewImmediate(view)
+ }
+
+ override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
+ val lp = (view.layoutParams as WindowManager.LayoutParams).apply {
+ this.x = x.toInt()
+ this.y = y.toInt()
+ }
+ view.layoutParams = lp
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewContainer.kt
new file mode 100644
index 000000000000..2650648a2cde
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewContainer.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.additionalviewcontainer
+
+import android.view.SurfaceControl
+import android.view.View
+import com.android.wm.shell.windowdecor.WindowDecoration
+
+/**
+ * Class for additional view containers associated with a [WindowDecoration].
+ */
+abstract class AdditionalViewContainer internal constructor(
+) {
+ abstract val view: View?
+
+ /** Release the view associated with this container and perform needed cleanup. */
+ abstract fun releaseView()
+
+ /** Reposition the view container using provided coordinates. */
+ abstract fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainer.kt
new file mode 100644
index 000000000000..222761260289
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainer.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.additionalviewcontainer
+
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import java.util.function.Supplier
+
+/**
+ * An [AdditionalViewContainer] that uses a [SurfaceControlViewHost] to show the window.
+ * Intended for view containers in freeform tasks that do not extend beyond task bounds.
+ */
+class AdditionalViewHostViewContainer(
+ private val windowSurface: SurfaceControl,
+ private val windowViewHost: SurfaceControlViewHost,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+) : AdditionalViewContainer() {
+
+ override val view
+ get() = windowViewHost.view
+
+ override fun releaseView() {
+ windowViewHost.release()
+ val t = transactionSupplier.get()
+ t.remove(windowSurface)
+ t.apply()
+ }
+
+ override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
+ t.setPosition(windowSurface, x, y)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
new file mode 100644
index 000000000000..5582e0f46321
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [HandleMenu].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:HandleMenuTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class HandleMenuTest : ShellTestCase() {
+ @JvmField
+ @Rule
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Mock
+ private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock
+ private lateinit var onClickListener: View.OnClickListener
+ @Mock
+ private lateinit var onTouchListener: View.OnTouchListener
+ @Mock
+ private lateinit var appIcon: Bitmap
+ @Mock
+ private lateinit var appName: CharSequence
+ @Mock
+ private lateinit var displayController: DisplayController
+ @Mock
+ private lateinit var splitScreenController: SplitScreenController
+ @Mock
+ private lateinit var displayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+
+ private lateinit var handleMenu: HandleMenu
+
+ @Before
+ fun setUp() {
+ val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
+ mock(SurfaceControl::class.java),
+ mockSurfaceControlViewHost,
+ ) {
+ SurfaceControl.Transaction()
+ }
+ val menuView = LayoutInflater.from(context).inflate(
+ R.layout.desktop_mode_window_decor_handle_menu, null)
+ whenever(mockDesktopWindowDecoration.addWindow(
+ anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockAdditionalViewHostViewContainer)
+ whenever(mockAdditionalViewHostViewContainer.view).thenReturn(menuView)
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
+ whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+ whenever(displayLayout.isLandscape).thenReturn(true)
+ mockDesktopWindowDecoration.mDecorWindowContext = context
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ fun testFullscreenMenuUsesSystemViewContainer() {
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
+ val handleMenu = createAndShowHandleMenu()
+ assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ // Verify menu is created at coordinates that, when added to WindowManager,
+ // show at the top-center of display.
+ assertTrue(handleMenu.mHandleMenuPosition.equals(16f, -512f))
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ fun testFreeformMenu_usesViewHostViewContainer() {
+ createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
+ handleMenu = createAndShowHandleMenu()
+ assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalViewHostViewContainer)
+ // Verify menu is created near top-left of task.
+ assertTrue(handleMenu.mHandleMenuPosition.equals(12f, 8f))
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ fun testSplitLeftMenu_usesSystemViewContainer() {
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
+ handleMenu = createAndShowHandleMenu()
+ assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ // Verify menu is created at coordinates that, when added to WindowManager,
+ // show at the top of split left task.
+ assertTrue(handleMenu.mHandleMenuPosition.equals(-624f, -512f))
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ fun testSplitRightMenu_usesSystemViewContainer() {
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
+ handleMenu = createAndShowHandleMenu()
+ assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ // Verify menu is created at coordinates that, when added to WindowManager,
+ // show at the top of split right task.
+ assertTrue(handleMenu.mHandleMenuPosition.equals(656f, -512f))
+ }
+
+ private fun createTaskInfo(windowingMode: Int, splitPosition: Int) {
+ val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW)
+ val bounds = when (windowingMode) {
+ WINDOWING_MODE_FULLSCREEN -> DISPLAY_BOUNDS
+ WINDOWING_MODE_FREEFORM -> FREEFORM_BOUNDS
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ SPLIT_LEFT_BOUNDS
+ } else {
+ SPLIT_RIGHT_BOUNDS
+ }
+ }
+ else -> error("Unsupported windowing mode")
+ }
+ mockDesktopWindowDecoration.mTaskInfo = TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setWindowingMode(windowingMode)
+ .setBounds(bounds)
+ .setVisible(true)
+ .build()
+ // Calculate captionX similar to how WindowDecoration calculates it.
+ whenever(mockDesktopWindowDecoration.captionX).thenReturn(
+ (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .bounds.width() - context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_decor_caption_width)) / 2)
+ whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition)
+ whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer {
+ (it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS)
+ }
+ }
+
+ private fun createAndShowHandleMenu(): HandleMenu {
+ val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
+ R.layout.desktop_mode_app_header
+ } else {
+ R.layout.desktop_mode_app_header
+ }
+ val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
+ onClickListener, onTouchListener, appIcon, appName, displayController,
+ splitScreenController, true /* shouldShowWindowingPill */,
+ 50 /* captionHeight */ )
+ handleMenu.show()
+ return handleMenu
+ }
+
+ companion object {
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2560, 1600)
+ private val FREEFORM_BOUNDS = Rect(500, 500, 2000, 1200)
+ private val SPLIT_LEFT_BOUNDS = Rect(0, 0, 1280, 1600)
+ private val SPLIT_RIGHT_BOUNDS = Rect(1280, 0, 2560, 1600)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 48310810e8c9..e73069ab52a7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -76,6 +76,7 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.tests.R;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
import org.junit.Before;
import org.junit.Test;
@@ -371,7 +372,7 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
- public void testAddWindow() {
+ public void testAddViewHostViewContainer() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
@@ -393,6 +394,7 @@ public class WindowDecorationTests extends ShellTestCase {
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
.setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
.setBounds(TASK_BOUNDS)
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
@@ -407,7 +409,7 @@ public class WindowDecorationTests extends ShellTestCase {
createMockSurfaceControlBuilder(additionalWindowSurface);
mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
- WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+ windowDecor.addTestViewContainer();
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
@@ -421,12 +423,6 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
- assertThat(additionalWindow.mWindowViewHost).isNotNull();
-
- additionalWindow.releaseView();
-
- assertThat(additionalWindow.mWindowViewHost).isNull();
- assertThat(additionalWindow.mWindowSurface).isNull();
}
@Test
@@ -905,16 +901,16 @@ public class WindowDecorationTests extends ShellTestCase {
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
- private WindowDecoration.AdditionalWindow addTestWindow() {
+ private AdditionalViewContainer addTestViewContainer() {
final Resources resources = mDecorWindowContext.getResources();
- int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
- int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
- String name = "Test Window";
- WindowDecoration.AdditionalWindow additionalWindow =
+ final int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
+ final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ final String name = "Test Window";
+ final AdditionalViewContainer additionalViewContainer =
addWindow(R.layout.desktop_mode_window_decor_handle_menu, name,
mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, 0 /* x */,
0 /* y */, width, height);
- return additionalWindow;
+ return additionalViewContainer;
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
new file mode 100644
index 000000000000..d3e996b12e1f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.additionalviewcontainer
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [AdditionalSystemViewContainer].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:AdditionalSystemViewContainerTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class AdditionalSystemViewContainerTest : ShellTestCase() {
+ @Mock
+ private lateinit var mockView: View
+ @Mock
+ private lateinit var mockLayoutInflater: LayoutInflater
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var mockWindowManager: WindowManager
+ private lateinit var viewContainer: AdditionalSystemViewContainer
+
+ @Before
+ fun setUp() {
+ whenever(mockContext.getSystemService(WindowManager::class.java))
+ .thenReturn(mockWindowManager)
+ whenever(mockContext.getSystemService(Context
+ .LAYOUT_INFLATER_SERVICE)).thenReturn(mockLayoutInflater)
+ whenever(mockLayoutInflater.inflate(
+ R.layout.desktop_mode_window_decor_handle_menu, null)).thenReturn(mockView)
+ }
+
+ @Test
+ fun testReleaseView_ViewRemoved() {
+ viewContainer = AdditionalSystemViewContainer(
+ mockContext,
+ R.layout.desktop_mode_window_decor_handle_menu,
+ TASK_ID,
+ X,
+ Y,
+ WIDTH,
+ HEIGHT
+ )
+ verify(mockWindowManager).addView(eq(mockView), any())
+ viewContainer.releaseView()
+ verify(mockWindowManager).removeViewImmediate(mockView)
+ }
+
+ companion object {
+ private const val X = 500
+ private const val Y = 50
+ private const val WIDTH = 400
+ private const val HEIGHT = 600
+ private const val TASK_ID = 5
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainerTest.kt
new file mode 100644
index 000000000000..82d557a28f52
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalViewHostViewContainerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.additionalviewcontainer
+
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import java.util.function.Supplier
+
+/**
+ * Tests for [AdditionalViewHostViewContainer].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:AdditionalViewHostViewContainerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AdditionalViewHostViewContainerTest : ShellTestCase() {
+ @Mock
+ private lateinit var mockTransactionSupplier: Supplier<SurfaceControl.Transaction>
+ @Mock
+ private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock
+ private lateinit var mockSurface: SurfaceControl
+ @Mock
+ private lateinit var mockViewHost: SurfaceControlViewHost
+ private lateinit var viewContainer: AdditionalViewHostViewContainer
+
+ @Before
+ fun setUp() {
+ whenever(mockTransactionSupplier.get()).thenReturn(mockTransaction)
+ }
+
+ @Test
+ fun testReleaseView_ViewRemoved() {
+ viewContainer = AdditionalViewHostViewContainer(
+ mockSurface,
+ mockViewHost,
+ mockTransactionSupplier
+ )
+ viewContainer.releaseView()
+ verify(mockViewHost).release()
+ verify(mockTransaction).remove(mockSurface)
+ verify(mockTransaction).apply()
+ }
+}