From e66b1ebec92f7caf43435ecd89dac4d437383b2e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 31 May 2017 20:50:47 -0700 Subject: [PATCH 1/5] Initial pointer capture work for O --- app/src/main/java/com/limelight/Game.java | 29 +++++++++++- .../AndroidNativePointerCaptureProvider.java | 46 +++++++++++++++++++ ...=> AndroidPointerIconCaptureProvider.java} | 4 +- .../input/capture/InputCaptureManager.java | 11 +++-- 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java rename app/src/main/java/com/limelight/binding/input/capture/{AndroidCaptureProvider.java => AndroidPointerIconCaptureProvider.java} (92%) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 8984c1c7..3d08ee14 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -170,6 +170,14 @@ public class Game extends Activity implements SurfaceHolder.Callback, streamView = (StreamView) findViewById(R.id.surfaceView); streamView.setOnGenericMotionListener(this); streamView.setOnTouchListener(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + streamView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() { + @Override + public boolean onCapturedPointer(View view, MotionEvent motionEvent) { + return handleMotionEvent(motionEvent, true); + } + }); + } // Warn the user if they're on a metered connection checkDataConnection(); @@ -277,6 +285,19 @@ public class Game extends Activity implements SurfaceHolder.Callback, streamView.getHolder().addCallback(this); } + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // If the window gains focus while we're grabbing input, + // we'll need to request for pointer capture again + if (grabbedInput) { + inputCaptureProvider.enableCapture(); + } + } + } + private void prepareDisplayForRendering() { Display display = getWindowManager().getDefaultDisplay(); WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes(); @@ -659,8 +680,12 @@ public class Game extends Activity implements SurfaceHolder.Callback, inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } - // Returns true if the event was consumed private boolean handleMotionEvent(MotionEvent event) { + return handleMotionEvent(event, false); + } + + // Returns true if the event was consumed + private boolean handleMotionEvent(MotionEvent event, boolean capturedMouseCallback) { // Pass through keyboard input if we're not grabbing if (!grabbedInput) { return false; @@ -714,7 +739,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, } // Get relative axis values if we can - if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) { + if (inputCaptureProvider.eventHasRelativeMouseAxes(event) || capturedMouseCallback) { // Send the deltas straight from the motion event conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event), (short) inputCaptureProvider.getRelativeAxisY(event)); diff --git a/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java new file mode 100644 index 00000000..d33cbc66 --- /dev/null +++ b/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java @@ -0,0 +1,46 @@ +package com.limelight.binding.input.capture; + +import android.annotation.TargetApi; +import android.os.Build; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.O) +public class AndroidNativePointerCaptureProvider extends InputCaptureProvider { + + private View targetView; + + public AndroidNativePointerCaptureProvider(View targetView) { + this.targetView = targetView; + } + + public static boolean isCaptureProviderSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + + @Override + public void enableCapture() { + targetView.requestPointerCapture(); + } + + @Override + public void disableCapture() { + targetView.releasePointerCapture(); + } + + @Override + public boolean eventHasRelativeMouseAxes(MotionEvent event) { + return (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) != 0; + } + + @Override + public float getRelativeAxisX(MotionEvent event) { + return event.getX(); + } + + @Override + public float getRelativeAxisY(MotionEvent event) { + return event.getY(); + } +} diff --git a/app/src/main/java/com/limelight/binding/input/capture/AndroidCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java similarity index 92% rename from app/src/main/java/com/limelight/binding/input/capture/AndroidCaptureProvider.java rename to app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java index f1133512..f5069d38 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/AndroidCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java @@ -10,11 +10,11 @@ import android.view.View; import android.view.ViewGroup; @TargetApi(Build.VERSION_CODES.N) -public class AndroidCaptureProvider extends InputCaptureProvider { +public class AndroidPointerIconCaptureProvider extends InputCaptureProvider { private ViewGroup rootViewGroup; private Context context; - public AndroidCaptureProvider(Activity activity) { + public AndroidPointerIconCaptureProvider(Activity activity) { this.context = activity; this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView(); } diff --git a/app/src/main/java/com/limelight/binding/input/capture/InputCaptureManager.java b/app/src/main/java/com/limelight/binding/input/capture/InputCaptureManager.java index 4a5f634b..320968ff 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/InputCaptureManager.java +++ b/app/src/main/java/com/limelight/binding/input/capture/InputCaptureManager.java @@ -3,6 +3,7 @@ package com.limelight.binding.input.capture; import android.app.Activity; import com.limelight.LimeLog; +import com.limelight.R; import com.limelight.binding.input.evdev.EvdevCaptureProvider; import com.limelight.binding.input.evdev.EvdevListener; @@ -15,13 +16,17 @@ public class InputCaptureManager { LimeLog.info("Using NVIDIA mouse capture extension"); return new ShieldCaptureProvider(activity); } + else if (AndroidNativePointerCaptureProvider.isCaptureProviderSupported()) { + LimeLog.info("Using Android O+ native mouse capture"); + return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView)); + } else if (EvdevCaptureProvider.isCaptureProviderSupported()) { LimeLog.info("Using Evdev mouse capture"); return new EvdevCaptureProvider(activity, rootListener); } - else if (AndroidCaptureProvider.isCaptureProviderSupported()) { - LimeLog.info("Using Android N+ native mouse capture"); - return new AndroidCaptureProvider(activity); + else if (AndroidPointerIconCaptureProvider.isCaptureProviderSupported()) { + LimeLog.info("Using Android N+ pointer hiding"); + return new AndroidPointerIconCaptureProvider(activity); } else { LimeLog.info("Mouse capture not available"); From f07c88671139fbe7e3f65f113ed3c31b24a9c19d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 31 May 2017 21:24:41 -0700 Subject: [PATCH 2/5] Add isCapturing() method to mouse capture providers --- .../AndroidNativePointerCaptureProvider.java | 9 ++++++++- .../AndroidPointerIconCaptureProvider.java | 2 ++ .../input/capture/InputCaptureProvider.java | 15 +++++++++++++-- .../input/capture/NullCaptureProvider.java | 8 +------- .../input/capture/ShieldCaptureProvider.java | 2 ++ .../binding/input/evdev/EvdevCaptureProvider.java | 2 ++ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java index d33cbc66..d2b16ae3 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/AndroidNativePointerCaptureProvider.java @@ -21,17 +21,24 @@ public class AndroidNativePointerCaptureProvider extends InputCaptureProvider { @Override public void enableCapture() { + super.enableCapture(); targetView.requestPointerCapture(); } @Override public void disableCapture() { + super.disableCapture(); targetView.releasePointerCapture(); } + @Override + public boolean isCapturing() { + return targetView.hasPointerCapture(); + } + @Override public boolean eventHasRelativeMouseAxes(MotionEvent event) { - return (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) != 0; + return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE; } @Override diff --git a/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java index f5069d38..6774bfdf 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/AndroidPointerIconCaptureProvider.java @@ -33,11 +33,13 @@ public class AndroidPointerIconCaptureProvider extends InputCaptureProvider { @Override public void enableCapture() { + super.enableCapture(); setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL)); } @Override public void disableCapture() { + super.disableCapture(); setPointerIconOnAllViews(null); } diff --git a/app/src/main/java/com/limelight/binding/input/capture/InputCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/InputCaptureProvider.java index 3de18c37..cea2bd61 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/InputCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/InputCaptureProvider.java @@ -3,10 +3,21 @@ package com.limelight.binding.input.capture; import android.view.MotionEvent; public abstract class InputCaptureProvider { - public abstract void enableCapture(); - public abstract void disableCapture(); + protected boolean isCapturing; + + public void enableCapture() { + isCapturing = true; + } + public void disableCapture() { + isCapturing = false; + } + public void destroy() {} + public boolean isCapturing() { + return isCapturing; + } + public boolean eventHasRelativeMouseAxes(MotionEvent event) { return false; } diff --git a/app/src/main/java/com/limelight/binding/input/capture/NullCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/NullCaptureProvider.java index 553816c0..d8d9cb80 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/NullCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/NullCaptureProvider.java @@ -1,10 +1,4 @@ package com.limelight.binding.input.capture; -public class NullCaptureProvider extends InputCaptureProvider { - @Override - public void enableCapture() {} - - @Override - public void disableCapture() {} -} +public class NullCaptureProvider extends InputCaptureProvider {} diff --git a/app/src/main/java/com/limelight/binding/input/capture/ShieldCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/capture/ShieldCaptureProvider.java index 98bbf7da..64e67573 100644 --- a/app/src/main/java/com/limelight/binding/input/capture/ShieldCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/capture/ShieldCaptureProvider.java @@ -63,11 +63,13 @@ public class ShieldCaptureProvider extends InputCaptureProvider { @Override public void enableCapture() { + super.enableCapture(); setCursorVisibility(false); } @Override public void disableCapture() { + super.disableCapture(); setCursorVisibility(true); } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevCaptureProvider.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevCaptureProvider.java index 8b60a2d1..47f8dd2a 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevCaptureProvider.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevCaptureProvider.java @@ -221,6 +221,7 @@ public class EvdevCaptureProvider extends InputCaptureProvider { @Override public void enableCapture() { + super.enableCapture(); if (!started) { // Start the handler thread if it's our first time // capturing @@ -247,6 +248,7 @@ public class EvdevCaptureProvider extends InputCaptureProvider { @Override public void disableCapture() { + super.disableCapture(); // This may be called on the main thread runInNetworkSafeContextSynchronously(new Runnable() { @Override From aa1b283570156b1f70db90b321be0bf5124de2ed Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 31 May 2017 21:46:53 -0700 Subject: [PATCH 3/5] Initial working pointer capture using onClick --- app/src/main/java/com/limelight/Game.java | 37 ++++++++++------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 3d08ee14..5c108a32 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -171,10 +171,16 @@ public class Game extends Activity implements SurfaceHolder.Callback, streamView.setOnGenericMotionListener(this); streamView.setOnTouchListener(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + streamView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + inputCaptureProvider.enableCapture(); + } + }); streamView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() { @Override public boolean onCapturedPointer(View view, MotionEvent motionEvent) { - return handleMotionEvent(motionEvent, true); + return handleMotionEvent(motionEvent); } }); } @@ -285,19 +291,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, streamView.getHolder().addCallback(this); } - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // If the window gains focus while we're grabbing input, - // we'll need to request for pointer capture again - if (grabbedInput) { - inputCaptureProvider.enableCapture(); - } - } - } - private void prepareDisplayForRendering() { Display display = getWindowManager().getDefaultDisplay(); WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes(); @@ -680,12 +673,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } - private boolean handleMotionEvent(MotionEvent event) { - return handleMotionEvent(event, false); - } - // Returns true if the event was consumed - private boolean handleMotionEvent(MotionEvent event, boolean capturedMouseCallback) { + private boolean handleMotionEvent(MotionEvent event) { // Pass through keyboard input if we're not grabbing if (!grabbedInput) { return false; @@ -696,8 +685,14 @@ public class Game extends Activity implements SurfaceHolder.Callback, return true; } } - else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) + else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 || + event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) { + // Ignore mouse input if we're not capturing from our input source + if (!inputCaptureProvider.isCapturing()) { + return false; + } + // This case is for mice if (event.getSource() == InputDevice.SOURCE_MOUSE || (event.getPointerCount() >= 1 && @@ -739,7 +734,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, } // Get relative axis values if we can - if (inputCaptureProvider.eventHasRelativeMouseAxes(event) || capturedMouseCallback) { + if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) { // Send the deltas straight from the motion event conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event), (short) inputCaptureProvider.getRelativeAxisY(event)); From a41318508549230f1d5434d0a588c082c3e34c1b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 31 May 2017 21:51:01 -0700 Subject: [PATCH 4/5] Fix Pixel C keyboard d-pad regression due to aliasing with SOURCE_GAMEPAD --- app/src/main/java/com/limelight/Game.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 5c108a32..ae931630 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -582,8 +582,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, boolean handled = false; boolean detectedGamepad = event.getDevice() == null ? false : - ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) != 0 || - (event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) != 0); + ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || + (event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD); if (detectedGamepad || (event.getDevice() == null || event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC )) { @@ -624,8 +624,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, boolean handled = false; boolean detectedGamepad = event.getDevice() == null ? false : - ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) != 0 || - (event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) != 0); + ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || + (event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD); if (detectedGamepad || (event.getDevice() == null || event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC )) { From 90a1e68c68d92db32bef28580206f71a10d1626c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 2 Jun 2017 18:17:18 -0700 Subject: [PATCH 5/5] Move input capture check to not mask touch events --- app/src/main/java/com/limelight/Game.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index ae931630..5e15d04f 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -688,11 +688,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 || event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) { - // Ignore mouse input if we're not capturing from our input source - if (!inputCaptureProvider.isCapturing()) { - return false; - } - // This case is for mice if (event.getSource() == InputDevice.SOURCE_MOUSE || (event.getPointerCount() >= 1 && @@ -700,6 +695,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, { int changedButtons = event.getButtonState() ^ lastButtonState; + // Ignore mouse input if we're not capturing from our input source + if (!inputCaptureProvider.isCapturing()) { + return false; + } + if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { // Send the vertical scroll packet byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);