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

Commit 2b210c23 authored by Tiger Huang's avatar Tiger Huang
Browse files

Reduce the window tap exclude region for child above it

For letting touches directly go to the embedded display, we've opened a
tap exclude region for the ActivityView. However, if there is a view on
top of the region, the view cannot be touched within the region.

In this CL, we reduce the tap exclude region if there is a can-receive-
pointer-event view on top of the region.

Bug: 128517544
Test: atest CtsActivityManagerDeviceTestCases:ActivityViewTest
Test: atest FrameworksCoreTests:ViewGroupTest
Test: Menual test with ActivityViewTest and Bubbles
Change-Id: I68e2a9fe9d0891801b533ab8d25074f64bef5a79
parent a50d69cc
Loading
Loading
Loading
Loading
+39 −10
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
@@ -46,6 +47,7 @@ import android.view.SurfaceSession;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;

@@ -81,6 +83,9 @@ public class ActivityView extends ViewGroup {
    // Temp container to store view coordinates in window.
    private final int[] mLocationInWindow = new int[2];

    // The latest tap exclude region that we've sent to WM.
    private final Region mTapExcludeRegion = new Region();

    private TaskStackListener mTaskStackListener;

    private final CloseGuard mGuard = CloseGuard.get();
@@ -279,11 +284,11 @@ public class ActivityView extends ViewGroup {
    }

    /**
     * Triggers an update of {@link ActivityView}'s location in window to properly set touch exclude
     * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
     * regions and avoid focus switches by touches on this view.
     */
    public void onLocationChanged() {
        updateLocation();
        updateTapExcludeRegion();
    }

    @Override
@@ -291,15 +296,38 @@ public class ActivityView extends ViewGroup {
        mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
    }

    /** Send current location and size to the WM to set tap exclude region for this view. */
    private void updateLocation() {
    @Override
    public boolean gatherTransparentRegion(Region region) {
        // The tap exclude region may be affected by any view on top of it, so we detect the
        // possible change by monitoring this function.
        updateTapExcludeRegion();
        return super.gatherTransparentRegion(region);
    }

    /** Compute and send current tap exclude region to WM for this view. */
    private void updateTapExcludeRegion() {
        if (!isAttachedToWindow()) {
            return;
        }
        if (!canReceivePointerEvents()) {
            cleanTapExcludeRegion();
            return;
        }
        try {
            getLocationInWindow(mLocationInWindow);
            final int x = mLocationInWindow[0];
            final int y = mLocationInWindow[1];
            mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());

            // There might be views on top of us. We need to subtract those areas from the tap
            // exclude region.
            final ViewParent parent = getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).subtractObscuredTouchableRegion(mTapExcludeRegion, this);
            }

            WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
                    mLocationInWindow[0], mLocationInWindow[1], getWidth(), getHeight());
                    mTapExcludeRegion);
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
@@ -322,7 +350,7 @@ public class ActivityView extends ViewGroup {
                mVirtualDisplay.setDisplayState(true);
            }

            updateLocation();
            updateTapExcludeRegion();
        }

        @Override
@@ -330,7 +358,7 @@ public class ActivityView extends ViewGroup {
            if (mVirtualDisplay != null) {
                mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
            }
            updateLocation();
            updateTapExcludeRegion();
        }

        @Override
@@ -460,13 +488,14 @@ public class ActivityView extends ViewGroup {

    /** Report to server that tap exclude region on hosting display should be cleared. */
    private void cleanTapExcludeRegion() {
        if (!isAttachedToWindow()) {
        if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
            return;
        }
        // Update tap exclude region with an empty rect to clean the state on server.
        // Update tap exclude region with a null region to clean the state on server.
        try {
            WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
                    0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */);
                    null /* region */);
            mTapExcludeRegion.setEmpty();
        } catch (RemoteException e) {
            e.rethrowAsRuntimeException();
        }
+4 −5
Original line number Diff line number Diff line
@@ -255,12 +255,11 @@ interface IWindowSession {
    void updatePointerIcon(IWindow window);

    /**
     * Update a tap exclude region with a rectangular area identified by provided id in the window.
     * Touches on this region will not switch focus to this window. Passing an empty rect will
     * remove the area from the exclude region of this window.
     * Update a tap exclude region identified by provided id in the window. Touches on this region
     * will neither be dispatched to this window nor change the focus to this window. Passing an
     * invalid region will remove the area from the exclude region of this window.
     */
    void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width,
            int height);
    void updateTapExcludeRegion(IWindow window, int regionId, in Region region);

    /**
     * Called when the client has changed the local insets state, and now the server should reflect
+10 −0
Original line number Diff line number Diff line
@@ -13898,6 +13898,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                .getAccessibilityFocusedHost() == this);
    }
    /**
     * Returns whether this view can receive pointer events.
     *
     * @return {@code true} if this view can receive pointer events.
     * @hide
     */
    protected boolean canReceivePointerEvents() {
        return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
    }
    /**
     * Filter the touch event to apply security policies.
     *
+45 −14
Original line number Diff line number Diff line
@@ -2011,7 +2011,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                if (!canViewReceivePointerEvents(child)
                if (!child.canReceivePointerEvents()
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    continue;
                }
@@ -2094,7 +2094,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                            childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);
                    if (!canViewReceivePointerEvents(child)
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        continue;
                    }
@@ -2314,7 +2314,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                                getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                        final View child =
                                getAndVerifyPreorderedView(preorderedList, children, childIndex);
                        if (!canViewReceivePointerEvents(child)
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
@@ -2500,7 +2500,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                if (!canViewReceivePointerEvents(child)
                if (!child.canReceivePointerEvents()
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    continue;
                }
@@ -2680,7 +2680,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
@@ -2970,15 +2970,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    /**
     * Returns true if a child view can receive pointer events.
     * @hide
     */
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

    private float[] getTempPoint() {
        if (mTempPoint == null) {
            mTempPoint = new float[2];
@@ -7199,6 +7190,46 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    /**
     * @hide
     */
    @Override
    public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
        final int childrenCount = mChildrenCount;
        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
        final View[] children = mChildren;
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if (child == view) {
                // We've reached the target view.
                break;
            }
            if (!child.canReceivePointerEvents()) {
                // This child cannot be touched. Skip it.
                continue;
            }
            applyOpToRegionByBounds(touchableRegion, child, Region.Op.DIFFERENCE);
        }

        // The touchable region should not exceed the bounds of its container.
        applyOpToRegionByBounds(touchableRegion, this, Region.Op.INTERSECT);

        final ViewParent parent = getParent();
        if (parent != null) {
            parent.subtractObscuredTouchableRegion(touchableRegion, this);
        }
    }

    private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
        final int[] locationInWindow = new int[2];
        view.getLocationInWindow(locationInWindow);
        final int x = locationInWindow[0];
        final int y = locationInWindow[1];
        region.op(x, y, x + view.getWidth(), y + view.getHeight(), op);
    }

    @Override
    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
        insets = super.dispatchApplyWindowInsets(insets);
+14 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.view;

import android.annotation.NonNull;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.view.accessibility.AccessibilityEvent;

@@ -660,4 +661,17 @@ public interface ViewParent {
     * @return true if the action was consumed by this ViewParent
     */
    public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);

    /**
     * Given a touchable region of a child, this method reduces region by the bounds of all views on
     * top of the child for which {@link View#canReceivePointerEvents} returns {@code true}. This
     * applies recursively for all views in the view hierarchy on top of this one.
     *
     * @param touchableRegion The touchable region we want to modify.
     * @param view A child view of this ViewGroup which indicates the z-order of the touchable
     *             region.
     * @hide
     */
    default void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
    }
}
Loading