Use inner frame of letterbox as touchable region for letterboxed app
Since commit 192fe76a, the letterbox is touchable. In order for the
touch event to be able to slip into the activity, the touchable region
of activity must not cover the letterbox.
Also toggle the token of window handle with surface show/hide to reduce
the inactive window records in InputDispatcher.
Bug: 110257889
Test: manual - Enable display cutout, open DocumentUI in landscape and
drag from the black region in the cutout side to DocumentUI, the
list view should scroll or the drawer slides out.
Change-Id: I4a0bffebfe7a3efcac842c0abbae60ea2a3487bd
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 220370c..2367ccc 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -3132,8 +3132,17 @@
}
}
+ /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
+ void getLetterboxInnerBounds(Rect outBounds) {
+ if (mLetterbox != null) {
+ outBounds.set(mLetterbox.getInnerFrame());
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
/**
- * @eturn true if there is a letterbox and any part of that letterbox overlaps with
+ * @return {@code true} if there is a letterbox and any part of that letterbox overlaps with
* the given {@code rect}.
*/
boolean isLetterboxOverlappingWith(Rect rect) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3110fb9..c3ea72f 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -92,6 +92,11 @@
mBottom.getHeight());
}
+ /** @return The frame that used to place the content. */
+ Rect getInnerFrame() {
+ return mInner;
+ }
+
/**
* Returns true if any part of the letterbox overlaps with the given {@code rect}.
*/
@@ -162,6 +167,7 @@
final InputWindowHandle mWindowHandle;
final InputEventReceiver mInputEventReceiver;
final WindowManagerService mWmService;
+ final Binder mToken = new Binder();
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
@@ -171,13 +177,12 @@
mClientChannel = channels[1];
mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
- final Binder token = new Binder();
- mWmService.mInputManager.registerInputChannel(mServerChannel, token);
+ mWmService.mInputManager.registerInputChannel(mServerChannel, mToken);
mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
null /* clientWindow */, win.getDisplayId());
mWindowHandle.name = name;
- mWindowHandle.token = token;
+ mWindowHandle.token = mToken;
mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
@@ -192,6 +197,14 @@
}
void updateTouchableRegion(Rect frame) {
+ if (frame.isEmpty()) {
+ // Use null token to indicate the surface doesn't need to receive input event (see
+ // the usage of Layer.hasInput in SurfaceFlinger), so InputDispatcher won't keep the
+ // unnecessary records.
+ mWindowHandle.token = null;
+ return;
+ }
+ mWindowHandle.token = mToken;
mWindowHandle.touchableRegion.set(frame);
mWindowHandle.touchableRegion.translate(-frame.left, -frame.top);
}
@@ -289,14 +302,14 @@
t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
- if (mInputInterceptor != null) {
- mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative);
- t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle);
- }
t.show(mSurface);
} else if (mSurface != null) {
t.hide(mSurface);
}
+ if (mSurface != null && mInputInterceptor != null) {
+ mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative);
+ t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle);
+ }
}
public boolean needsApplySurfaceChanges() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 20cca66..c31764a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2180,16 +2180,22 @@
if (modal && mAppToken != null) {
// Limit the outer touch to the activity stack region.
flags |= FLAG_NOT_TOUCH_MODAL;
- // If this is a modal window we need to dismiss it if it's not full screen and the
- // touch happens outside of the frame that displays the content. This means we
- // need to intercept touches outside of that window. The dim layer user
- // associated with the window (task or stack) will give us the good bounds, as
- // they would be used to display the dim layer.
- final Task task = getTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
- } else {
- getStack().getDimBounds(mTmpRect);
+ // If the inner bounds of letterbox is available, then it will be used as the touchable
+ // region so it won't cover the touchable letterbox and the touch events can slip to
+ // activity from letterbox.
+ mAppToken.getLetterboxInnerBounds(mTmpRect);
+ if (mTmpRect.isEmpty()) {
+ // If this is a modal window we need to dismiss it if it's not full screen and the
+ // touch happens outside of the frame that displays the content. This means we need
+ // to intercept touches outside of that window. The dim layer user associated with
+ // the window (task or stack) will give us the good bounds, as they would be used to
+ // display the dim layer.
+ final Task task = getTask();
+ if (task != null) {
+ task.getDimBounds(mTmpRect);
+ } else {
+ getStack().getDimBounds(mTmpRect);
+ }
}
if (inFreeformWindowingMode()) {
// For freeform windows we the touch region to include the whole surface for the