diff options
8 files changed, 109 insertions, 39 deletions
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 81251b81e239..aa43c8dab2ad 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 @@ -114,6 +114,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; private final SplitScreenController mSplitScreenController; + private final WindowManagerWrapper mWindowManagerWrapper; private WindowDecorationViewHolder mWindowDecorViewHolder; private View.OnClickListener mOnCaptionButtonClickListener; @@ -188,7 +189,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, - SurfaceControl::new, new SurfaceControlViewHostFactory() {}, + SurfaceControl::new, new WindowManagerWrapper( + context.getSystemService(WindowManager.class)), + new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper); } @@ -211,6 +214,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, + WindowManagerWrapper windowManagerWrapper, SurfaceControlViewHostFactory surfaceControlViewHostFactory, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, @@ -229,6 +233,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMaximizeMenuFactory = maximizeMenuFactory; mHandleMenuFactory = handleMenuFactory; mMultiInstanceHelper = multiInstanceHelper; + mWindowManagerWrapper = windowManagerWrapper; } /** @@ -574,7 +579,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return new AppHandleViewHolder( mResult.mRootView, mOnCaptionTouchListener, - mOnCaptionButtonClickListener + mOnCaptionButtonClickListener, + mWindowManagerWrapper ); } else if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_header) { @@ -988,6 +994,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin updateGenericLink(); mHandleMenu = mHandleMenuFactory.create( this, + mWindowManagerWrapper, mRelayoutParams.mLayoutResId, mAppIconBitmap, mAppName, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 34de94eb3352..748046ebd08d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -64,6 +64,7 @@ import com.android.wm.shell.windowdecor.extension.isPinned */ class HandleMenu( private val parentDecor: DesktopModeWindowDecoration, + private val windowManagerWrapper: WindowManagerWrapper, private val layoutResId: Int, private val appIconBitmap: Bitmap?, private val appName: CharSequence?, @@ -178,7 +179,7 @@ class HandleMenu( handleMenuViewContainer = if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) { AdditionalSystemViewContainer( - context = context, + windowManagerWrapper = windowManagerWrapper, taskId = taskInfo.taskId, x = x, y = y, @@ -635,6 +636,7 @@ class HandleMenu( interface HandleMenuFactory { fun create( parentDecor: DesktopModeWindowDecoration, + windowManagerWrapper: WindowManagerWrapper, layoutResId: Int, appIconBitmap: Bitmap?, appName: CharSequence?, @@ -652,6 +654,7 @@ interface HandleMenuFactory { object DefaultHandleMenuFactory : HandleMenuFactory { override fun create( parentDecor: DesktopModeWindowDecoration, + windowManagerWrapper: WindowManagerWrapper, layoutResId: Int, appIconBitmap: Bitmap?, appName: CharSequence?, @@ -665,6 +668,7 @@ object DefaultHandleMenuFactory : HandleMenuFactory { ): HandleMenu { return HandleMenu( parentDecor, + windowManagerWrapper, layoutResId, appIconBitmap, appName, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt new file mode 100644 index 000000000000..5c2ff1b1647c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt @@ -0,0 +1,41 @@ +/* + * 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.view.View +import android.view.WindowManager + +/** + * A wrapper for [WindowManager] to make view manipulation operations related to window + * decors more testable. + */ +class WindowManagerWrapper ( + private val windowManager: WindowManager +){ + + fun addView(v: View, lp: WindowManager.LayoutParams) { + windowManager.addView(v, lp) + } + + fun removeViewImmediate(v: View) { + windowManager.removeViewImmediate(v) + } + + fun updateViewLayout(v: View, lp: WindowManager.LayoutParams) { + windowManager.updateViewLayout(v, lp) + } +}
\ No newline at end of file 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 index cadd80e70190..226b0fb2e1a1 100644 --- 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 @@ -24,13 +24,14 @@ import android.view.LayoutInflater import android.view.SurfaceControl import android.view.View import android.view.WindowManager +import com.android.wm.shell.windowdecor.WindowManagerWrapper /** * An [AdditionalViewContainer] that uses the system [WindowManager] instance. Intended * for view containers that should be above the status bar layer. */ class AdditionalSystemViewContainer( - context: Context, + private val windowManagerWrapper: WindowManagerWrapper, taskId: Int, x: Int, y: Int, @@ -39,9 +40,20 @@ class AdditionalSystemViewContainer( flags: Int, override val view: View ) : AdditionalViewContainer() { + val lp: WindowManager.LayoutParams = WindowManager.LayoutParams( + width, height, x, y, + WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, + flags, + PixelFormat.TRANSPARENT + ).apply { + title = "Additional view container of Task=$taskId" + gravity = Gravity.LEFT or Gravity.TOP + setTrustedOverlay() + } constructor( context: Context, + windowManagerWrapper: WindowManagerWrapper, taskId: Int, x: Int, y: Int, @@ -50,7 +62,7 @@ class AdditionalSystemViewContainer( flags: Int, @LayoutRes layoutId: Int ) : this( - context = context, + windowManagerWrapper = windowManagerWrapper, taskId = taskId, x = x, y = y, @@ -61,9 +73,16 @@ class AdditionalSystemViewContainer( ) constructor( - context: Context, taskId: Int, x: Int, y: Int, width: Int, height: Int, flags: Int + context: Context, + windowManagerWrapper: WindowManagerWrapper, + taskId: Int, + x: Int, + y: Int, + width: Int, + height: Int, + flags: Int ) : this( - context = context, + windowManagerWrapper = windowManagerWrapper, taskId = taskId, x = x, y = y, @@ -73,24 +92,12 @@ class AdditionalSystemViewContainer( view = View(context) ) - val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java) - init { - val lp = WindowManager.LayoutParams( - width, height, x, y, - WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, - flags, - PixelFormat.TRANSPARENT - ).apply { - title = "Additional view container of Task=$taskId" - gravity = Gravity.LEFT or Gravity.TOP - setTrustedOverlay() - } - windowManager?.addView(view, lp) + windowManagerWrapper.addView(view, lp) } override fun releaseView() { - windowManager?.removeViewImmediate(view) + windowManagerWrapper.removeViewImmediate(view) } override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) { @@ -98,6 +105,6 @@ class AdditionalSystemViewContainer( this.x = x.toInt() this.y = y.toInt() } - windowManager?.updateViewLayout(view, lp) + windowManagerWrapper.updateViewLayout(view, lp) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 510032ba4480..dfa5ab415992 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -32,6 +32,7 @@ import android.widget.ImageButton import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.shared.animation.Interpolators +import com.android.wm.shell.windowdecor.WindowManagerWrapper import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer /** @@ -41,14 +42,14 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem internal class AppHandleViewHolder( rootView: View, onCaptionTouchListener: View.OnTouchListener, - onCaptionButtonClickListener: OnClickListener + onCaptionButtonClickListener: OnClickListener, + private val windowManagerWrapper: WindowManagerWrapper ) : WindowDecorationViewHolder(rootView) { companion object { private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100 } private lateinit var taskInfo: RunningTaskInfo - private val windowManager = context.getSystemService(WindowManager::class.java) private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) private val inputManager = context.getSystemService(InputManager::class.java) @@ -96,11 +97,12 @@ internal class AppHandleViewHolder( handleWidth: Int, handleHeight: Int) { if (!Flags.enableAdditionalWindowsAboveStatusBar()) return - statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId, - handlePosition.x, handlePosition.y, handleWidth, handleHeight, + statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper, + taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) - val view = statusBarInputLayer?.view - val lp = view?.layoutParams as WindowManager.LayoutParams + val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View") + val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer" + + "LayoutParams") lp.title = "Handle Input Layer of task " + taskInfo.taskId lp.setTrustedOverlay() // Make this window a spy window to enable it to pilfer pointers from the system-wide @@ -120,7 +122,7 @@ internal class AppHandleViewHolder( captionHandle.dispatchTouchEvent(event) true } - windowManager.updateViewLayout(view, lp) + windowManagerWrapper.updateViewLayout(view, lp) } private fun updateStatusBarInputLayer(globalPosition: Point) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 596adfb98a70..258c86094a36 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -173,9 +173,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private AppToWebGenericLinksParser mMockGenericLinksParser; @Mock + private WindowManager mMockWindowManager; + @Mock private HandleMenu mMockHandleMenu; @Mock private HandleMenuFactory mMockHandleMenuFactory; + @Mock private MultiInstanceHelper mMockMultiInstanceHelper; @Captor private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; @@ -220,9 +223,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); - when(mMockHandleMenuFactory.create(any(), anyInt(), any(), any(), + when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); } @After @@ -385,6 +389,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void updateRelayoutParams_fullscreen_inputChannelNotNeeded() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -401,6 +406,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void updateRelayoutParams_multiwindow_inputChannelNotNeeded() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); @@ -491,7 +497,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { .isTrue(); } - @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) + @Test public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -518,7 +524,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -530,7 +535,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_fullscreenTask_postsViewHostCreation() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -559,7 +563,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_removesExistingHandlerCallback() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -574,7 +577,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void close_removesExistingHandlerCallback() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -826,7 +828,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } private void verifyHandleMenuCreated(@Nullable Uri uri) { - verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), + verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt()); } @@ -894,8 +896,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, + new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory, - mMockMultiInstanceHelper); + mMockMultiInstanceHelper); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); 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 index 627dfe718521..100abbbb8332 100644 --- 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 @@ -33,6 +33,7 @@ import android.view.LayoutInflater import android.view.SurfaceControl import android.view.SurfaceControlViewHost import android.view.View +import android.view.WindowManager import androidx.core.graphics.toPointF import androidx.test.filters.SmallTest import com.android.window.flags.Flags @@ -77,6 +78,8 @@ class HandleMenuTest : ShellTestCase() { @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration @Mock + private lateinit var mockWindowManager: WindowManager + @Mock private lateinit var onClickListener: View.OnClickListener @Mock private lateinit var onTouchListener: View.OnTouchListener @@ -230,8 +233,9 @@ class HandleMenuTest : ShellTestCase() { } else -> error("Invalid windowing mode") } - val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, appIcon, appName, - splitScreenController, shouldShowWindowingPill = true, + val handleMenu = HandleMenu(mockDesktopWindowDecoration, + WindowManagerWrapper(mockWindowManager), + layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true, shouldShowNewWindowButton = true, null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50, captionX = captionX 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 index 28b4eb6e8ab1..0f52ed7f1c02 100644 --- 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 @@ -25,6 +25,7 @@ import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.wm.shell.R import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.windowdecor.WindowManagerWrapper import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -70,6 +71,7 @@ class AdditionalSystemViewContainerTest : ShellTestCase() { WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH viewContainer = AdditionalSystemViewContainer( mockContext, + WindowManagerWrapper(mockWindowManager), TASK_ID, X, Y, |