OSDN Git Service

Support touch event on letterbox surface
authorRiddle Hsu <riddlehsu@google.com>
Tue, 15 Jan 2019 15:41:57 +0000 (23:41 +0800)
committerRiddle Hsu <riddlehsu@google.com>
Sun, 10 Feb 2019 09:55:37 +0000 (17:55 +0800)
This is part of "Compatible behavior for non-resizable activity".
It allows window to receive motion event when sliding from a letterbox
surface. That makes it easier for users to slide out the side menu of
a letterboxed activity.

Note this only works when the touchable region of the activity doesn't
cover letterbox surface (lower layer than activity).

Bug: 112288258
Test: manual - Start an activity with fixed aspect ratio and make sure
      it touchable region matches its content size, and then slide from
      letterbox region into the activity, it should receive touch event.
Change-Id: Ibff9103adbdad0ae5dd93c4a44b00f41d06c1007

services/core/java/com/android/server/wm/AppWindowToken.java
services/core/java/com/android/server/wm/Letterbox.java

index bcf6aba..9a8943c 100644 (file)
@@ -884,6 +884,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
             dc.setFocusedApp(null);
             mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
         }
+        if (mLetterbox != null) {
+            mLetterbox.destroy();
+            mLetterbox = null;
+        }
 
         if (!delayed) {
             updateReportedVisibilityLocked();
@@ -1297,6 +1301,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
                 }
             }
         }
+
+        if (prevDc != mDisplayContent && mLetterbox != null) {
+            mLetterbox.onMovedToDisplay(mDisplayContent.getDisplayId());
+        }
     }
 
     /**
@@ -1844,6 +1852,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
         if (needsLetterbox) {
             if (mLetterbox == null) {
                 mLetterbox = new Letterbox(() -> makeChildSurface(null));
+                mLetterbox.attachInput(w);
             }
             getPosition(mTmpPoint);
             mLetterbox.layout(getParent().getBounds(), w.getFrameLw(), mTmpPoint);
index 9874920..d67193e 100644 (file)
@@ -20,7 +20,15 @@ import static android.view.SurfaceControl.HIDDEN;
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Process;
+import android.view.InputChannel;
+import android.view.InputEventReceiver;
+import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import com.android.server.UiThread;
 
 import java.util.function.Supplier;
 
@@ -40,6 +48,7 @@ public class Letterbox {
     private final LetterboxSurface mLeft = new LetterboxSurface("left");
     private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
     private final LetterboxSurface mRight = new LetterboxSurface("right");
+    private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
 
     /**
      * Constructs a Letterbox.
@@ -87,8 +96,12 @@ public class Letterbox {
      * Returns true if any part of the letterbox overlaps with the given {@code rect}.
      */
     public boolean isOverlappingWith(Rect rect) {
-        return mTop.isOverlappingWith(rect) || mLeft.isOverlappingWith(rect)
-                || mBottom.isOverlappingWith(rect) || mRight.isOverlappingWith(rect);
+        for (LetterboxSurface surface : mSurfaces) {
+            if (surface.isOverlappingWith(rect)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -107,25 +120,94 @@ public class Letterbox {
         mOuter.setEmpty();
         mInner.setEmpty();
 
-        mTop.remove();
-        mLeft.remove();
-        mBottom.remove();
-        mRight.remove();
+        for (LetterboxSurface surface : mSurfaces) {
+            surface.remove();
+        }
     }
 
     /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
     public boolean needsApplySurfaceChanges() {
-        return mTop.needsApplySurfaceChanges()
-                || mLeft.needsApplySurfaceChanges()
-                || mBottom.needsApplySurfaceChanges()
-                || mRight.needsApplySurfaceChanges();
+        for (LetterboxSurface surface : mSurfaces) {
+            if (surface.needsApplySurfaceChanges()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public void applySurfaceChanges(SurfaceControl.Transaction t) {
-        mTop.applySurfaceChanges(t);
-        mLeft.applySurfaceChanges(t);
-        mBottom.applySurfaceChanges(t);
-        mRight.applySurfaceChanges(t);
+        for (LetterboxSurface surface : mSurfaces) {
+            surface.applySurfaceChanges(t);
+        }
+    }
+
+    /** Enables touches to slide into other neighboring surfaces. */
+    void attachInput(WindowState win) {
+        for (LetterboxSurface surface : mSurfaces) {
+            surface.attachInput(win);
+        }
+    }
+
+    void onMovedToDisplay(int displayId) {
+        for (LetterboxSurface surface : mSurfaces) {
+            if (surface.mInputInterceptor != null) {
+                surface.mInputInterceptor.mWindowHandle.displayId = displayId;
+            }
+        }
+    }
+
+    private static class InputInterceptor {
+        final InputChannel mServerChannel;
+        final InputChannel mClientChannel;
+        final InputWindowHandle mWindowHandle;
+        final InputEventReceiver mInputEventReceiver;
+        final WindowManagerService mWmService;
+
+        InputInterceptor(String namePrefix, WindowState win) {
+            mWmService = win.mWmService;
+            final String name = namePrefix + (win.mAppToken != null ? win.mAppToken : win);
+            final InputChannel[] channels = InputChannel.openInputChannelPair(name);
+            mServerChannel = channels[0];
+            mClientChannel = channels[1];
+            mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
+
+            final Binder token = new Binder();
+            mWmService.mInputManager.registerInputChannel(mServerChannel, token);
+
+            mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
+                    null /* clientWindow */, win.getDisplayId());
+            mWindowHandle.name = name;
+            mWindowHandle.token = token;
+            mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                    | WindowManager.LayoutParams.FLAG_SLIPPERY;
+            mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+            mWindowHandle.dispatchingTimeoutNanos =
+                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mWindowHandle.visible = true;
+            mWindowHandle.ownerPid = Process.myPid();
+            mWindowHandle.ownerUid = Process.myUid();
+            mWindowHandle.scaleFactor = 1.0f;
+        }
+
+        void updateTouchableRegion(Rect frame) {
+            mWindowHandle.touchableRegion.set(frame);
+            mWindowHandle.touchableRegion.translate(-frame.left, -frame.top);
+        }
+
+        void dispose() {
+            mWmService.mInputManager.unregisterInputChannel(mServerChannel);
+            mInputEventReceiver.dispose();
+            mServerChannel.dispose();
+            mClientChannel.dispose();
+        }
+
+        private static class SimpleInputReceiver extends InputEventReceiver {
+            SimpleInputReceiver(InputChannel inputChannel) {
+                super(inputChannel, UiThread.getHandler().getLooper());
+            }
+        }
     }
 
     private class LetterboxSurface {
@@ -137,6 +219,8 @@ public class Letterbox {
         private final Rect mLayoutFrameGlobal = new Rect();
         private final Rect mLayoutFrameRelative = new Rect();
 
+        private InputInterceptor mInputInterceptor;
+
         public LetterboxSurface(String type) {
             mType = type;
         }
@@ -154,11 +238,22 @@ public class Letterbox {
             mSurface.setColor(new float[]{0, 0, 0});
         }
 
+        void attachInput(WindowState win) {
+            if (mInputInterceptor != null) {
+                mInputInterceptor.dispose();
+            }
+            mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win);
+        }
+
         public void remove() {
             if (mSurface != null) {
                 mSurface.remove();
                 mSurface = null;
             }
+            if (mInputInterceptor != null) {
+                mInputInterceptor.dispose();
+                mInputInterceptor = null;
+            }
         }
 
         public int getWidth() {
@@ -193,6 +288,10 @@ public class Letterbox {
                 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);