diff options
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() +    } +}  |