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

Commit e1e7fe05 authored by Jorge Gil's avatar Jorge Gil
Browse files

Make the caption window spy when requesting custom headers

The INPUT_FEATURE_SPY had been removed and replaced by custom caption
touchable regions because the latter was an a11y-friendly alternative.

It turns out that while that is still true (a11y services can see the
custom content when setting touchable region carveouts but not when
just making the caption window spy), removing the spy feature also
caused a regression when the app's top window is transparent or
semi-transparent modal. In such cases, the caption is still transparent
and the custom content is still visible through the caption and through
the modal, so the intended UX is that touching any area of the
customizable caption would dismiss the modal, but the custom touchable
region in this case is not set because the modal prevents the gesture
exclusion regions from being reported to SystemUI.

By making the window spy, we let the first motion event fall through
before pilfering, which is enough to dismiss the modal through the
caption window.

Also fixes a bug in wd2 where |checkTouchEventInCustomizableRegion| only
worked for AppHandleController but not AppHeaderController.

Flag: com.android.window.flags.enable_accessible_custom_headers
Fix: 414521306
Test: Open chrome in desktop with custom headers, open the 3-dot menu on
the top-right of the search/toolbar, then tap on any part of the
customizable caption region, customized or not (so a tab or the empty
space) and verify the 3-dot dialog menu is dismissed.

Change-Id: Ia5ac6b2658145dfb18cc27ad3fce1647e0714dbc
parent ab2878df
Loading
Loading
Loading
Loading
+20 −6
Original line number Diff line number Diff line
@@ -432,11 +432,25 @@ constructor(
        var shouldSetAppBounds = false
        if (isAppHeader) {
            if (taskInfo.isTransparentCaptionBarAppearance) {
                if (!DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue) {
                    // Allow input to fall through to the windows below so that the app can respond
                    // to input events on their custom content.
                // The app is requesting to customize the caption bar, which means input on
                // customizable/exclusion regions must go to the app instead of to the system.
                // Custom touchable regions OR spy windows are usually sufficient to satisfy this
                // requirement for the general case, but some edge cases make it so we actually
                // need both:
                // 1) Spy window by itself does not let a11y services "see" through the window and
                // focus the custom content. The touchable region carveout helps here. Note that
                // this is set by |CaptionController#calculateLimitedTouchableRegion|.
                // 2) When the app has a modal window on top of the window that reports exclusion
                // regions, the modal window actually blocks the exclusion region from being
                // reported to SystemUI, which prevents the window decoration from correctly
                // setting the touchable region (of the caption) and thus touching the
                // custom region has the input consumed by the caption and makes it impossible for
                // the modal to be closed in this region, see b/414521306.
                // So by setting the spy feature the input can fall through to the windows below,
                // but more precisely it allows the first motion event over a modal window to fall
                // through and dismiss the modal, even when the caption touchable region is not
                // being limited.
                inputFeatures = inputFeatures or WindowManager.LayoutParams.INPUT_FEATURE_SPY
                }
            } else if (DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue) {
                if (shouldExcludeCaptionFromAppBounds) {
                    shouldSetAppBounds = true
@@ -857,7 +871,7 @@ constructor(

    /** Checks if touch event occurred in caption's customizable region. */
    fun checkTouchEventInCustomizableRegion(e: MotionEvent): Boolean =
        (captionController as? AppHandleController)?.checkTouchEventInCustomizableRegion(e) ?: false
        captionController?.checkTouchEventInCustomizableRegion(e) ?: false

    /** Adds inset for caption if one exists. */
    fun addCaptionInset(wct: WindowContainerTransaction) {
+1 −5
Original line number Diff line number Diff line
@@ -114,9 +114,6 @@ public class DesktopModeTouchEventListener
     * Whether to pilfer the next motion event to send cancellations to the windows below.
     * Useful when the caption window is spy and the gesture should be handled by the system
     * instead of by the app for their custom header content.
     * Should not have any effect when
     * {@link DesktopModeFlags#ENABLE_ACCESSIBLE_CUSTOM_HEADERS}, because a spy window is not
     * used then.
     */
    private boolean mIsCustomHeaderGesture;
    private boolean mIsResizeGesture;
@@ -337,8 +334,7 @@ public class DesktopModeTouchEventListener
                    viewName, mIsCustomHeaderGesture, mIsResizeGesture);
            return false;
        }
        if (mInputManager != null
                && !DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue()) {
        if (mInputManager != null) {
            ViewRootImpl viewRootImpl = v.getViewRootImpl();
            if (viewRootImpl != null) {
                // Pilfer so that windows below receive cancellations for this gesture.
+16 −5
Original line number Diff line number Diff line
@@ -1090,18 +1090,29 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
                // The app is requesting to customize the caption bar, which means input on
                // customizable/exclusion regions must go to the app instead of to the system.
                // This may be accomplished with spy windows or custom touchable regions:
                // Custom touchable regions OR spy windows are usually sufficient to satisfy this
                // requirement for the general case, but some edge cases make it so we actually
                // need both:
                // 1) Spy window by itself does not let a11y services "see" through the window and
                // focus the custom content. The touchable region carveout helps here.
                // 2) When the app has a modal window on top of the window that reports exclusion
                // regions, the modal window actually blocks the exclusion region from being
                // reported to SystemUI, which prevents the window decoration from correctly
                // setting the touchable region (of the caption) and thus touching the custom
                // region has the input consumed by the caption and makes it impossible for the
                // modal to be closed in this region, see b/414521306.
                if (DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue()) {
                    // Set the touchable region of the caption to only the areas where input should
                    // be handled by the system (i.e. non custom-excluded areas). The region will
                    // be calculated based on occluding caption elements and exclusion areas
                    // reported by the app.
                    relayoutParams.mLimitTouchRegionToSystemAreas = true;
                } else {
                    // Allow input to fall through to the windows below so that the app can respond
                    // to input events on their custom content.
                    relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
                }
                // Also allow input to fall through to the windows below so that the app can
                // respond to input events on their custom content, but more precisely to allow
                // the first motion event over a modal window to fall through and dismiss the modal,
                // even when the caption touchable region is not being limited.
                relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
            } else {
                if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) {
                    if (shouldExcludeCaptionFromAppBounds) {
+2 −6
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
import android.window.DesktopModeFlags
import android.window.WindowContainerTransaction
import com.android.app.tracing.traceSection
import com.android.internal.protolog.ProtoLog
@@ -280,11 +279,8 @@ abstract class CaptionController<T>(
        decorWindowContext: Context,
        localCaptionBounds: Rect,
    ): Region? {
        // If app is not requesting custom caption, touchable region is not limited so return null
        if (
            !taskInfo.isTransparentCaptionBarAppearance ||
                !DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue
        ) {
        if (!taskInfo.isTransparentCaptionBarAppearance) {
            // App is not requesting custom caption, touchable region is not limited so return null.
            return null
        }

+0 −3
Original line number Diff line number Diff line
@@ -663,7 +663,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
    public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -691,7 +690,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
    public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -717,7 +715,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
    public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() {
        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);