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

Commit 234c2592 authored by Robert Carr's avatar Robert Carr Committed by Rob Carr
Browse files

AttachedSurfaceControl: Add setTouchableRegion API

Add an API for setting touchable regions. This API allows
a view hierarchy to allow touches to pass through its
surface in certain regions. Touch pass through of course
follows the regular rules about UID/PID origins and
touchable region does not effect the calculation of the
TOUCH_OBSCURED flag. Such an API has existed as @hide
since the beginning of time, via
ViewTreeObserver.ComputeInternalInsetsListener. The API has already been
wrapped in the IME system, and in VoiceInteractionSession.
Desire to make the API public is motivated by 3 use cases:
	1. GameOverlayService UI code moving out of framework
	required for their UX (multiple small buttons in a large
	transparent overlay)
	2. SurfaceControlViewHost: A common point request among
	people who ask me about SurfaceControlViewHost is to be able
	to embed the SurfaceControlViewHost in a SurfaceView which is
	"setZOrderOnTop(false)" (hole punching) so that the host
	hierarchy can draw on top of the embedded hierarchy in some
	regions. This is possible today, but there will be no way
	to receive input without input hole punching.
	3. There may be cases where apps are using multiple Popup type
	windows which could be combined in to one. In general reducing
	HWC layer count this way will be a good trade off.

It was considered to ViewTreeObserver.ComputeInternalInsetsListener
directly but ultimately decided against it for a few reasons:
	1. Obscure naming
	2. Other fields not useful for our use cases.
	   Because insets/touchable region linkage not a useful API concept for
	   us
	3. Forces dispatch model via callbacks which may or may not be
           useful for a given app.

It was also considered to create a gatherTouchableRegion API mirroring
gatherTransparentRegion. Such a concept (as setting root surface
touchable regions) is not really composable though. For example
maybe a parent view requires touch regions, then a child view
one day starts removing them. The fact that input order is not
simply reverse drawing order (since parents come before children
in both) means there is no way to ensure this always cleanly composes
as expected. Given this, it's better to expose a "global" API
and require the app developer to understand how their components
are fitting together, rather than invite a View implementer
to subclass gatherTouchableRegion and allow someone to break them.

Bug: 213603716
Bug: 214239892
Test: SetTouchableRegionTest, SurfaceControlViewHostTests
Change-Id: Ie49caec964455b7129daa2aaa6990a368309f8e9
parent aeaaa42c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -48313,6 +48313,7 @@ package android.view {
    method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
    method public default int getBufferTransformHint();
    method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
    method public default void setTouchableRegion(@Nullable android.graphics.Region);
  }
  @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+14 −0
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@ package android.view;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UiThread;
import android.graphics.Region;
import android.hardware.HardwareBuffer;

/**
@@ -124,4 +126,16 @@ public interface AttachedSurfaceControl {
    default void removeOnBufferTransformHintChangedListener(
            @NonNull OnBufferTransformHintChangedListener listener) {
    }

    /**
     * Sets the touchable region for this SurfaceControl, expressed in surface local
     * coordinates. By default the touchable region is the entire Layer, indicating
     * that if the layer is otherwise eligible to receive touch it receives touch
     * on the entire surface. Setting the touchable region allows the SurfaceControl
     * to receive touch in some regions, while allowing for pass-through in others.
     *
     * @param r The region to use or null to use the entire Layer bounds
     */
    default void setTouchableRegion(@Nullable Region r) {
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -342,4 +342,9 @@ interface IWindowSession {
     * @param callback The {@link IOnBackInvokedCallback} to set.
     */
    oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);

    /**
     * Clears a touchable region set by {@link #setInsets}.
     */
    void clearTouchableRegion(IWindow window);
}
+61 −9
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import static android.view.ViewRootImplProto.VISIBLE_RECT;
import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -471,6 +472,9 @@ public final class ViewRootImpl implements ViewParent,
    final Region mTransparentRegion;
    final Region mPreviousTransparentRegion;

    Region mTouchableRegion;
    Region mPreviousTouchableRegion;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    int mWidth;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -3249,9 +3253,15 @@ public final class ViewRootImpl implements ViewParent,
            mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
        }

        Rect contentInsets = null;
        Rect visibleInsets = null;
        Region touchableRegion = null;
        int touchableInsetMode = TOUCHABLE_INSETS_REGION;
        boolean computedInternalInsets = false;
        if (computesInternalInsets) {
            // Clear the original insets.
            final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;

            // Clear the original insets.
            insets.reset();

            // Compute new insets in place.
@@ -3263,9 +3273,6 @@ public final class ViewRootImpl implements ViewParent,
                mLastGivenInsets.set(insets);

                // Translate insets to screen coordinates if needed.
                final Rect contentInsets;
                final Rect visibleInsets;
                final Region touchableRegion;
                if (mTranslator != null) {
                    contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
                    visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
@@ -3275,12 +3282,46 @@ public final class ViewRootImpl implements ViewParent,
                    visibleInsets = insets.visibleInsets;
                    touchableRegion = insets.touchableRegion;
                }

                computedInternalInsets = true;
            }
            touchableInsetMode = insets.mTouchableInsets;
        }
        boolean needsSetInsets = computedInternalInsets;
        needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
            (mTouchableRegion != null);
        if (needsSetInsets) {
            if (mTouchableRegion != null) {
                if (mPreviousTouchableRegion == null) {
                    mPreviousTouchableRegion = new Region();
                }
                mPreviousTouchableRegion.set(mTouchableRegion);
                if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
                    Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
                          " from OnComputeInternalInsets, while also using setTouchableRegion" +
                          " causes setTouchableRegion to be ignored");
                }
            } else {
                mPreviousTouchableRegion = null;
            }
            if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
            if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
            if (touchableRegion == null) {
                touchableRegion = mTouchableRegion;
            } else if (touchableRegion != null && mTouchableRegion != null) {
                touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
            }
            try {
                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                mWindowSession.setInsets(mWindow, touchableInsetMode,
                                         contentInsets, visibleInsets, touchableRegion);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
            mPreviousTouchableRegion = null;
            try {
                mWindowSession.clearTouchableRegion(mWindow);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

@@ -10701,4 +10742,15 @@ public final class ViewRootImpl implements ViewParent,
        }
        return mFallbackOnBackInvokedDispatcher;
    }

    @Override
    public void setTouchableRegion(Region r) {
        if (r != null) {
            mTouchableRegion = new Region(r);
        } else {
            mTouchableRegion = null;
        }
        mLastGivenInsets.reset();
        requestLayout();
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -334,6 +334,11 @@ public class WindowlessWindowManager implements IWindowSession {
        setTouchRegion(window.asBinder(), touchableRegion);
    }

    @Override
    public void clearTouchableRegion(android.view.IWindow window) {
        setTouchRegion(window.asBinder(), null);
    }

    @Override
    public void finishDrawing(android.view.IWindow window,
            android.view.SurfaceControl.Transaction postDrawTransaction) {
Loading