diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 00d049a9..fb25aaf6 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -16,6 +16,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer; import com.limelight.nvstream.input.KeyboardPacket; import com.limelight.nvstream.input.MouseButtonPacket; import com.limelight.preferences.PreferenceConfiguration; +import com.limelight.ui.GameGestures; import com.limelight.utils.Dialog; import com.limelight.utils.SpinnerDialog; @@ -30,6 +31,7 @@ import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; +import android.os.SystemClock; import android.preference.PreferenceManager; import android.view.Display; import android.view.InputDevice; @@ -44,6 +46,7 @@ import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import android.widget.Toast; import java.util.Locale; @@ -51,7 +54,7 @@ import java.util.Locale; public class Game extends Activity implements SurfaceHolder.Callback, OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener, - OnSystemUiVisibilityChangeListener + OnSystemUiVisibilityChangeListener, GameGestures { private int lastMouseX = Integer.MIN_VALUE; private int lastMouseY = Integer.MIN_VALUE; @@ -59,6 +62,9 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Only 2 touches are supported private TouchContext[] touchContextMap = new TouchContext[2]; + private long threeFingerDownTime = 0; + + private static final int THREE_FINGER_TAP_THRESHOLD = 300; private ControllerHandler controllerHandler; private KeyboardTranslator keybTranslator; @@ -189,7 +195,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Initialize the connection conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this)); keybTranslator = new KeyboardTranslator(conn); - controllerHandler = new ControllerHandler(conn, prefConfig.deadzonePercentage); + controllerHandler = new ControllerHandler(conn, this, prefConfig.deadzonePercentage); SurfaceHolder sh = sv.getHolder(); if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) { @@ -480,6 +486,13 @@ public class Game extends Activity implements SurfaceHolder.Callback, } } + @Override + public void showKeyboard() { + LimeLog.info("Showing keyboard overlay"); + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + } + // Returns true if the event was consumed private boolean handleMotionEvent(MotionEvent event) { // Pass through keyboard input if we're not grabbing @@ -501,7 +514,22 @@ public class Game extends Activity implements SurfaceHolder.Callback, int actionIndex = event.getActionIndex(); int eventX = (int)event.getX(actionIndex); - int eventY = (int)event.getY(actionIndex); + int eventY = (int)event.getY(actionIndex); + + // Special handling for 3 finger gesture + if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN && + event.getPointerCount() == 3) { + // Three fingers down + threeFingerDownTime = SystemClock.uptimeMillis(); + + // Cancel the first and second touches to avoid + // erroneous events + for (TouchContext aTouchContext : touchContextMap) { + aTouchContext.cancelTouch(); + } + + return true; + } TouchContext context = getTouchContext(actionIndex); if (context == null) { @@ -516,8 +544,16 @@ public class Game extends Activity implements SurfaceHolder.Callback, break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: + if (event.getPointerCount() == 1) { + // All fingers up + if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) { + // This is a 3 finger tap to bring up the keyboard + showKeyboard(); + return true; + } + } context.touchUpEvent(eventX, eventY); - if (actionIndex == 0 && event.getPointerCount() > 1) { + if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) { // The original secondary touch now becomes primary context.touchDownEvent((int)event.getX(1), (int)event.getY(1)); } diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java index 3b7fb54d..df9f13a3 100644 --- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java +++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java @@ -10,6 +10,7 @@ import android.view.MotionEvent; import com.limelight.LimeLog; import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.ControllerPacket; +import com.limelight.ui.GameGestures; import com.limelight.utils.Vector2d; public class ControllerHandler { @@ -30,6 +31,9 @@ public class ControllerHandler { private long lastLbUpTime = 0; private long lastRbUpTime = 0; private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100; + + private long startDownTime = 0; + private static final int START_DOWN_TIME_KEYB_MS = 750; private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; @@ -46,10 +50,12 @@ public class ControllerHandler { private NvConnection conn; private double stickDeadzone; private final ControllerMapping defaultMapping = new ControllerMapping(); + private GameGestures gestures; private boolean hasGameController; - public ControllerHandler(NvConnection conn, int deadzonePercentage) { + public ControllerHandler(NvConnection conn, GameGestures gestures, int deadzonePercentage) { this.conn = conn; + this.gestures = gestures; // HACK: For now we're hardcoding a 10% deadzone. Some deadzone // is required for controller batching support to work. @@ -513,6 +519,9 @@ public class ControllerHandler { break; case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_MENU: + if (SystemClock.uptimeMillis() - startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) { + gestures.showKeyboard(); + } inputMap &= ~ControllerPacket.PLAY_FLAG; break; case KeyEvent.KEYCODE_BACK: @@ -621,6 +630,9 @@ public class ControllerHandler { break; case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_MENU: + if (event.getRepeatCount() == 0) { + startDownTime = SystemClock.uptimeMillis(); + } inputMap |= ControllerPacket.PLAY_FLAG; break; case KeyEvent.KEYCODE_BACK: diff --git a/app/src/main/java/com/limelight/binding/input/TouchContext.java b/app/src/main/java/com/limelight/binding/input/TouchContext.java index 91996c4b..87a38249 100644 --- a/app/src/main/java/com/limelight/binding/input/TouchContext.java +++ b/app/src/main/java/com/limelight/binding/input/TouchContext.java @@ -9,6 +9,7 @@ public class TouchContext { private int originalTouchX = 0; private int originalTouchY = 0; private long originalTouchTime = 0; + private boolean cancelled; private NvConnection conn; private int actionIndex; @@ -56,12 +57,17 @@ public class TouchContext { originalTouchX = lastTouchX = eventX; originalTouchY = lastTouchY = eventY; originalTouchTime = System.currentTimeMillis(); - - return true; + cancelled = false; + + return true; } public void touchUpEvent(int eventX, int eventY) { + if (cancelled) { + return; + } + if (isTap()) { byte buttonIndex = getMouseButtonIndex(); @@ -81,8 +87,8 @@ public class TouchContext { } public boolean touchMoveEvent(int eventX, int eventY) - { - if (eventX != lastTouchX || eventY != lastTouchY) + { + if (eventX != lastTouchX || eventY != lastTouchY) { // We only send moves for the primary touch point if (actionIndex == 0) { @@ -102,4 +108,12 @@ public class TouchContext { return true; } + + public void cancelTouch() { + cancelled = true; + } + + public boolean isCancelled() { + return cancelled; + } }