summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author mattsziklay <mattsziklay@google.com> 2024-09-26 11:38:46 -0700
committer mattsziklay <mattsziklay@google.com> 2024-10-21 15:35:49 -0700
commit3ae88e64aadbc74ff3407e713344e7323a1247e5 (patch)
tree82eb48b95d1ae9351a58bd36dcbd28de50e6195e
parent5af7c7b1c7a09afe52da79798dce9367727611fa (diff)
Set up status bar input layers on init.
Improves performance by caching two status bar input layer views on init of the view model. This avoids creating and destroying the views every time a task appears or disappears. Instead of doing that, the AppHandleViewHolder sets the input layer's position and touch/hover listeners as needed. Bug: 369465173 Bug: 369534904 Test: ABTD perf metrics Flag: com.android.window.flags.enable_handle_input_fix Change-Id: If9974b363e4787f566167c28f2159495593f87a1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt92
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt109
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java10
7 files changed, 272 insertions, 118 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 a06b4a2e09d4..bfddaaa34132 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
@@ -128,6 +128,7 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -178,6 +179,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+ private DesktopStatusBarInputLayerSupplier mStatusBarInputLayerSupplier;
private final ExclusionRegionListener mExclusionRegionListener =
new ExclusionRegionListenerImpl();
@@ -411,7 +413,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return Unit.INSTANCE;
});
}
- mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
+ if (Flags.enableHandleInputFix()) {
+ mStatusBarInputLayerSupplier =
+ new DesktopStatusBarInputLayerSupplier(mContext, mMainHandler);
+ mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
+ }
}
@Override
@@ -467,6 +473,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
+ decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
mActivityOrientationChangeHandler.ifPresent(handler ->
handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
@@ -505,6 +512,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
+ decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -644,7 +652,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.closeHandleMenu();
// When the app enters split-select, the handle will no longer be visible, meaning
// we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
+ decoration.detachStatusBarInputLayer();
mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
}
@@ -1280,8 +1288,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// should not be receiving any input.
if (resultType == TO_SPLIT_LEFT_INDICATOR
|| resultType == TO_SPLIT_RIGHT_INDICATOR) {
- relevantDecor.disposeStatusBarInputLayer();
- // We should also dispose the other split task's input layer if
+ relevantDecor.detachStatusBarInputLayer();
+ // We should also detach the other split task's input layer if
// applicable.
final int splitPosition = mSplitScreenController
.getSplitPosition(relevantDecor.mTaskInfo.taskId);
@@ -1294,7 +1302,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mSplitScreenController.getTaskInfo(oppositePosition);
if (oppositeTaskInfo != null) {
mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .disposeStatusBarInputLayer();
+ .detachStatusBarInputLayer();
}
}
}
@@ -1538,6 +1546,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(taskPositioner);
+ windowDecoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -1546,6 +1555,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
}
+ /** Decide which cached status bar input layer should be used for a decoration. */
+ private AdditionalSystemViewContainer getStatusBarInputLayer(
+ RunningTaskInfo taskInfo
+ ) {
+ if (mStatusBarInputLayerSupplier == null) return null;
+ return mStatusBarInputLayerSupplier.getStatusBarInputLayer(
+ taskInfo,
+ mSplitScreenController.getSplitPosition(taskInfo.taskId),
+ mSplitScreenController.isLeftRightSplit()
+ );
+ }
+
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
.getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
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 6eb20b9e3ae5..2b08ba0d7ab2 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
@@ -101,6 +101,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -197,6 +198,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopRepository mDesktopRepository;
+ private AdditionalSystemViewContainer mStatusBarInputLayer;
DesktopModeWindowDecoration(
Context context,
@@ -494,13 +496,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
notifyNoCaptionHandle();
}
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
- disposeStatusBarInputLayer();
+ detachStatusBarInputLayer();
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
return;
}
if (oldRootView != mResult.mRootView) {
- disposeStatusBarInputLayer();
+ detachStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
@@ -518,6 +520,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
isCaptionVisible()
));
+ if (mStatusBarInputLayer != null) {
+ asAppHandle(mWindowDecorViewHolder).bindStatusBarInputLayer(mStatusBarInputLayer);
+ }
} else {
mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
mTaskInfo,
@@ -727,15 +732,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
- * Dispose of the view used to forward inputs in status bar region. Intended to be
+ * Detach the status bar input layer from this decoration. Intended to be
* used any time handle is no longer visible.
*/
- void disposeStatusBarInputLayer() {
+ void detachStatusBarInputLayer() {
if (!isAppHandle(mWindowDecorViewHolder)
|| !Flags.enableHandleInputFix()) {
return;
}
- asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
+ asAppHandle(mWindowDecorViewHolder).detachStatusBarInputLayer();
}
private WindowDecorationViewHolder createViewHolder() {
@@ -1563,7 +1568,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeManageWindowsMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
- disposeStatusBarInputLayer();
+ detachStatusBarInputLayer();
clearCurrentViewHostRunnable();
if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
notifyNoCaptionHandle();
@@ -1680,6 +1685,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
+ "}";
}
+ /**
+ * Set the view container to be used to forward input through status bar. Null in cases
+ * where input forwarding isn't needed.
+ */
+ public void setStatusBarInputLayer(
+ @Nullable AdditionalSystemViewContainer additionalSystemViewContainer
+ ) {
+ mStatusBarInputLayer = additionalSystemViewContainer;
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
new file mode 100644
index 000000000000..025bb403ec8b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.Handler
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.split.SplitScreenConstants
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+
+/**
+ * Supplier for [AdditionalSystemViewContainer] objects to be used for forwarding input
+ * events through status bar to an app handle. Currently supports two simultaneous input layers.
+ *
+ * The supplier will pick one of two input layer view containers to use: one for tasks in
+ * fullscreen or top/left split stage, and one for tasks in right split stage.
+ */
+class DesktopStatusBarInputLayerSupplier(
+ private val context: Context,
+ @ShellMainThread handler: Handler
+) {
+ private val inputLayers: MutableList<AdditionalSystemViewContainer> = mutableListOf()
+
+ init {
+ // Post this as creation of the input layer views is a relatively expensive operation.
+ handler.post {
+ repeat(TOTAL_INPUT_LAYERS) {
+ inputLayers.add(createInputLayer())
+ }
+ }
+ }
+
+ private fun createInputLayer(): AdditionalSystemViewContainer {
+ val lp = WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT
+ )
+ lp.title = "Desktop status bar input layer"
+ lp.gravity = Gravity.LEFT or Gravity.TOP
+ lp.setTrustedOverlay()
+
+ // Make this window a spy window to enable it to pilfer pointers from the system-wide
+ // gesture listener that receives events before window. This is to prevent notification
+ // shade gesture when we swipe down to enter desktop.
+ lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ val view = View(context)
+ view.visibility = View.INVISIBLE
+ return AdditionalSystemViewContainer(
+ WindowManagerWrapper(
+ context.getSystemService<WindowManager>(WindowManager::class.java)
+ ),
+ view,
+ lp
+ )
+ }
+
+ /**
+ * Decide which cached status bar input layer should be used for a decoration, if any.
+ *
+ * [splitPosition] and [isLeftRightSplit] are used to determine which input layer we use.
+ * The first one is reserved for fullscreen tasks or tasks in top/left split,
+ * while the second one is exclusively used for tasks in right split stage. Note we care about
+ * left-right vs top-bottom split as the bottom stage should not use an input layer.
+ */
+ fun getStatusBarInputLayer(
+ taskInfo: RunningTaskInfo,
+ @SplitScreenConstants.SplitPosition splitPosition: Int,
+ isLeftRightSplit: Boolean
+ ): AdditionalSystemViewContainer? {
+ if (!taskInfo.isVisibleRequested) return null
+ // Fullscreen and top/left split tasks will use the first input layer.
+ if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+ || splitPosition == SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+ ) {
+ return inputLayers[LEFT_TOP_INPUT_LAYER]
+ }
+ // Right split tasks will use the second one.
+ if (isLeftRightSplit && splitPosition == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ) {
+ return inputLayers[RIGHT_SPLIT_INPUT_LAYER]
+ }
+ // Which leaves bottom split and freeform tasks, which do not need an input layer
+ // as the status bar is not blocking them.
+ return null
+ }
+
+ companion object {
+ private const val TOTAL_INPUT_LAYERS = 2
+ // Input layer index for fullscreen tasks and tasks in top-left split
+ private const val LEFT_TOP_INPUT_LAYER = 0
+ // Input layer index for tasks in right split stage. Does not include bottom split as that
+ // stage is not blocked by status bar.
+ private const val RIGHT_SPLIT_INPUT_LAYER = 1
+ }
+}
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 8b6aaaf619e0..1451f363ec73 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
@@ -23,8 +23,8 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.View
-import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams
import com.android.wm.shell.windowdecor.WindowManagerWrapper
/**
@@ -33,27 +33,11 @@ import com.android.wm.shell.windowdecor.WindowManagerWrapper
*/
class AdditionalSystemViewContainer(
private val windowManagerWrapper: WindowManagerWrapper,
- taskId: Int,
- x: Int,
- y: Int,
- width: Int,
- height: Int,
- flags: Int,
- @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0,
- override val view: View
+ override val view: View,
+ val lp: LayoutParams
) : 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()
- this.forciblyShownTypes = forciblyShownTypes
- }
+ /** Provide a layout id of a view to inflate for this view container. */
constructor(
context: Context,
windowManagerWrapper: WindowManagerWrapper,
@@ -66,15 +50,30 @@ class AdditionalSystemViewContainer(
@LayoutRes layoutId: Int
) : this(
windowManagerWrapper = windowManagerWrapper,
- taskId = taskId,
- x = x,
- y = y,
- width = width,
- height = height,
- flags = flags,
- view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
+ view = LayoutInflater.from(context).inflate(layoutId, null /* parent */),
+ lp = createLayoutParams(x, y, width, height, flags, taskId)
)
+ /** Provide a view directly for this view container */
+ constructor(
+ windowManagerWrapper: WindowManagerWrapper,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ view: View,
+ forciblyShownTypes: Int = 0
+ ) : this(
+ windowManagerWrapper = windowManagerWrapper,
+ view = view,
+ lp = createLayoutParams(x, y, width, height, flags, taskId).apply {
+ this.forciblyShownTypes = forciblyShownTypes
+ }
+ )
+
+ /** Do not supply a view at all, instead creating the view container with a basic view. */
constructor(
context: Context,
windowManagerWrapper: WindowManagerWrapper,
@@ -86,12 +85,7 @@ class AdditionalSystemViewContainer(
flags: Int
) : this(
windowManagerWrapper = windowManagerWrapper,
- taskId = taskId,
- x = x,
- y = y,
- width = width,
- height = height,
- flags = flags,
+ lp = createLayoutParams(x, y, width, height, flags, taskId),
view = View(context)
)
@@ -104,7 +98,7 @@ class AdditionalSystemViewContainer(
}
override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
- val lp = (view.layoutParams as WindowManager.LayoutParams).apply {
+ lp.apply {
this.x = x.toInt()
this.y = y.toInt()
}
@@ -124,13 +118,29 @@ class AdditionalSystemViewContainer(
): AdditionalSystemViewContainer =
AdditionalSystemViewContainer(
windowManagerWrapper = windowManagerWrapper,
- taskId = taskId,
- x = x,
- y = y,
- width = width,
- height = height,
- flags = flags,
- view = view
+ view = view,
+ lp = createLayoutParams(x, y, width, height, flags, taskId)
)
}
+ companion object {
+ fun createLayoutParams(
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ taskId: Int
+ ): LayoutParams {
+ return LayoutParams(
+ width, height, x, y,
+ LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+ flags,
+ PixelFormat.TRANSPARENT
+ ).apply {
+ title = "Additional view container of Task=$taskId"
+ gravity = Gravity.LEFT or Gravity.TOP
+ setTrustedOverlay()
+ }
+ }
+ }
}
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 b5700ffb046b..b43a9839f042 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
@@ -36,13 +36,10 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
-import com.android.internal.policy.SystemBarUtils
-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
-import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
@@ -69,10 +66,12 @@ internal class AppHandleViewHolder(
) : Data()
private lateinit var taskInfo: RunningTaskInfo
+ private val position: Point = Point()
+ private var width: Int = 0
+ private var height: Int = 0
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)
- private var statusBarInputLayerExists = false
// An invisible View that takes up the same coordinates as captionHandle but is layered
// above the status bar. The purpose of this View is to receive input intended for
@@ -112,21 +111,54 @@ internal class AppHandleViewHolder(
) {
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
this.taskInfo = taskInfo
- // If handle is not in status bar region(i.e., bottom stage in vertical split),
- // do not create an input layer
- if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible && statusBarInputLayerExists) {
- disposeStatusBarInputLayer()
+ this.position.set(position)
+ this.width = width
+ this.height = height
+ if (!isCaptionVisible && statusBarInputLayer != null) {
+ detachStatusBarInputLayer()
return
}
- // Input layer view creation / modification takes a significant amount of time;
+ }
+
+ fun bindStatusBarInputLayer(
+ statusBarLayer: AdditionalSystemViewContainer
+ ) {
+ // Input layer view modification takes a significant amount of time;
// post them so we don't hold up DesktopModeWindowDecoration#relayout.
- if (statusBarInputLayerExists) {
+ if (statusBarLayer == statusBarInputLayer) {
handler.post { updateStatusBarInputLayer(position) }
- } else {
- // Input layer is created on a delay; prevent multiple from being created.
- statusBarInputLayerExists = true
- handler.post { createStatusBarInputLayer(position, width, height) }
+ return
+ }
+ // Remove the old input layer when changing to a new one.
+ if (statusBarInputLayer != null) detachStatusBarInputLayer()
+ if (statusBarLayer.view.visibility == View.INVISIBLE) {
+ statusBarLayer.view.visibility = View.VISIBLE
+ }
+ statusBarInputLayer = statusBarLayer
+ statusBarInputLayer?.let {
+ inputLayer -> setupAppHandleA11y(inputLayer.view)
+ }
+ handler.post {
+ val view = statusBarInputLayer?.view
+ ?: error("Unable to find statusBarInputLayer View")
+ // Caption handle is located within the status bar region, meaning the
+ // DisplayPolicy will attempt to transfer this input to status bar if it's
+ // a swipe down. Pilfer here to keep the gesture in handle alone.
+ view.setOnTouchListener { v, event ->
+ if (event.actionMasked == ACTION_DOWN) {
+ inputManager.pilferPointers(v.viewRootImpl.inputToken)
+ }
+ captionHandle.dispatchTouchEvent(event)
+ return@setOnTouchListener true
+ }
+ view.setOnHoverListener { _, event ->
+ captionHandle.onHoverEvent(event)
+ }
+ val lp = statusBarInputLayer?.view?.layoutParams as WindowManager.LayoutParams
+ lp.x = position.x
+ lp.y = position.y
+ lp.width = width
+ lp.height = height
}
}
@@ -138,40 +170,6 @@ internal class AppHandleViewHolder(
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
- private fun createStatusBarInputLayer(handlePosition: Point,
- handleWidth: Int,
- handleHeight: Int) {
- if (!Flags.enableHandleInputFix()) return
- statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
- taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- )
- 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
- // gesture listener that receives events before window. This is to prevent notification
- // shade gesture when we swipe down to enter desktop.
- lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
- view.setOnHoverListener { _, event ->
- captionHandle.onHoverEvent(event)
- }
- // Caption handle is located within the status bar region, meaning the
- // DisplayPolicy will attempt to transfer this input to status bar if it's
- // a swipe down. Pilfer here to keep the gesture in handle alone.
- view.setOnTouchListener { v, event ->
- if (event.actionMasked == ACTION_DOWN) {
- inputManager.pilferPointers(v.viewRootImpl.inputToken)
- }
- captionHandle.dispatchTouchEvent(event)
- return@setOnTouchListener true
- }
- setupAppHandleA11y(view)
- windowManagerWrapper.updateViewLayout(view, lp)
- }
-
private fun setupAppHandleA11y(view: View) {
view.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
@@ -224,15 +222,12 @@ internal class AppHandleViewHolder(
}
/**
- * Remove the input layer from [WindowManager]. Should be used when caption handle
- * is not visible.
+ * Remove the input listeners from the input layer and remove it from this view holder.
*/
- fun disposeStatusBarInputLayer() {
- statusBarInputLayerExists = false
- handler.post {
- statusBarInputLayer?.releaseView()
- statusBarInputLayer = null
- }
+ fun detachStatusBarInputLayer() {
+ statusBarInputLayer?.view?.setOnTouchListener(null)
+ statusBarInputLayer?.view?.setOnHoverListener(null)
+ statusBarInputLayer = null
}
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 175fbd2396e3..470974e80c6b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -909,7 +909,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
+ fun testDecor_onClickToSplitScreen_detachesStatusBarInputLayer() {
val toSplitScreenListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -919,7 +919,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
toSplitScreenListenerCaptor.value.invoke()
- verify(decor).disposeStatusBarInputLayer()
+ verify(decor).detachStatusBarInputLayer()
}
@Test
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 320887212f54..3ec6eaaf0d91 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
@@ -49,7 +49,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.kotlin.VerificationKt.times;
import android.app.ActivityManager;
import android.app.assist.AssistContent;
@@ -847,8 +846,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
+ verify(mMockHandler).post(runnableArgument.capture());
runnableArgument.getValue().run();
verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
}
@@ -875,8 +873,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
+ verify(mMockHandler).post(runnableArgument.capture());
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
@@ -890,8 +887,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
+ verify(mMockHandler).post(runnableArgument.capture());
spyWindowDecor.close();