Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ae88e64 authored by mattsziklay's avatar mattsziklay
Browse files

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
parent 5af7c7b1
Loading
Loading
Loading
Loading
+26 −5
Original line number Diff line number Diff line
@@ -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,8 +413,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                        return Unit.INSTANCE;
                    });
        }
        if (Flags.enableHandleInputFix()) {
            mStatusBarInputLayerSupplier =
                    new DesktopStatusBarInputLayerSupplier(mContext, mMainHandler);
            mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
        }
    }

    @Override
    public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
@@ -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
+21 −6
Original line number Diff line number Diff line
@@ -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(
+117 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
+51 −41
Original line number Diff line number Diff line
@@ -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,29 +33,29 @@ import com.android.wm.shell.windowdecor.WindowManagerWrapper
 */
class AdditionalSystemViewContainer(
    private val windowManagerWrapper: WindowManagerWrapper,
    override val view: View,
    val lp: LayoutParams
) : AdditionalViewContainer() {

    /** Provide a layout id of a view to inflate for this view container. */
    constructor(
        context: Context,
        windowManagerWrapper: WindowManagerWrapper,
        taskId: Int,
        x: Int,
        y: Int,
        width: Int,
        height: Int,
        flags: Int,
    @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0,
    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()
        this.forciblyShownTypes = forciblyShownTypes
    }
        @LayoutRes layoutId: Int
    ) : this(
        windowManagerWrapper = windowManagerWrapper,
        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(
        context: Context,
        windowManagerWrapper: WindowManagerWrapper,
        taskId: Int,
        x: Int,
@@ -63,18 +63,17 @@ class AdditionalSystemViewContainer(
        width: Int,
        height: Int,
        flags: Int,
        @LayoutRes layoutId: Int
        view: View,
        forciblyShownTypes: Int = 0
    ) : this(
        windowManagerWrapper = windowManagerWrapper,
        taskId = taskId,
        x = x,
        y = y,
        width = width,
        height = height,
        flags = flags,
        view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
        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()
            }
        }
    }
}
+52 −57
Original line number Diff line number Diff line
@@ -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,52 +111,36 @@ 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;
        // post them so we don't hold up DesktopModeWindowDecoration#relayout.
        if (statusBarInputLayerExists) {
            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) }
        }
    }

    override fun onHandleMenuOpened() {
        animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
    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 (statusBarLayer == statusBarInputLayer) {
            handler.post { updateStatusBarInputLayer(position) }
            return
        }

    override fun onHandleMenuClosed() {
        animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
        // 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
        }

    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)
        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.
@@ -168,8 +151,23 @@ internal class AppHandleViewHolder(
                captionHandle.dispatchTouchEvent(event)
                return@setOnTouchListener true
            }
        setupAppHandleA11y(view)
        windowManagerWrapper.updateViewLayout(view, lp)
            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
        }
    }

    override fun onHandleMenuOpened() {
        animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
    }

    override fun onHandleMenuClosed() {
        animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
    }

    private fun setupAppHandleA11y(view: View) {
@@ -224,16 +222,13 @@ 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()
    fun detachStatusBarInputLayer() {
        statusBarInputLayer?.view?.setOnTouchListener(null)
        statusBarInputLayer?.view?.setOnHoverListener(null)
        statusBarInputLayer = null
    }
    }

    private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
        return if (shouldUseLightCaptionColors(taskInfo)) {
Loading