package com.limelight.binding.input; import android.view.View; import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.MouseButtonPacket; import java.util.Timer; import java.util.TimerTask; public class TouchContext { private int lastTouchX = 0; private int lastTouchY = 0; private int originalTouchX = 0; private int originalTouchY = 0; private long originalTouchTime = 0; private boolean cancelled; private boolean confirmedMove; private boolean confirmedDrag; private Timer dragTimer; private double distanceMoved; private double xFactor, yFactor; private final NvConnection conn; private final int actionIndex; private final int referenceWidth; private final int referenceHeight; private final View targetView; private static final int TAP_MOVEMENT_THRESHOLD = 20; private static final int TAP_DISTANCE_THRESHOLD = 25; private static final int TAP_TIME_THRESHOLD = 250; private static final int DRAG_TIME_THRESHOLD = 650; public TouchContext(NvConnection conn, int actionIndex, int referenceWidth, int referenceHeight, View view) { this.conn = conn; this.actionIndex = actionIndex; this.referenceWidth = referenceWidth; this.referenceHeight = referenceHeight; this.targetView = view; } public int getActionIndex() { return actionIndex; } private boolean isWithinTapBounds(int touchX, int touchY) { int xDelta = Math.abs(touchX - originalTouchX); int yDelta = Math.abs(touchY - originalTouchY); return xDelta <= TAP_MOVEMENT_THRESHOLD && yDelta <= TAP_MOVEMENT_THRESHOLD; } private boolean isTap() { long timeDelta = System.currentTimeMillis() - originalTouchTime; return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD; } private byte getMouseButtonIndex() { if (actionIndex == 1) { return MouseButtonPacket.BUTTON_RIGHT; } else { return MouseButtonPacket.BUTTON_LEFT; } } public boolean touchDownEvent(int eventX, int eventY) { // Get the view dimensions to scale inputs on this touch xFactor = referenceWidth / (double)targetView.getWidth(); yFactor = referenceHeight / (double)targetView.getHeight(); originalTouchX = lastTouchX = eventX; originalTouchY = lastTouchY = eventY; originalTouchTime = System.currentTimeMillis(); cancelled = confirmedDrag = confirmedMove = false; distanceMoved = 0; if (actionIndex == 0) { // Start the timer for engaging a drag startDragTimer(); } return true; } public void touchUpEvent(int eventX, int eventY) { if (cancelled) { return; } // Cancel the drag timer cancelDragTimer(); byte buttonIndex = getMouseButtonIndex(); if (confirmedDrag) { // Raise the button after a drag conn.sendMouseButtonUp(buttonIndex); } else if (isTap()) { // Lower the mouse button conn.sendMouseButtonDown(buttonIndex); // We need to sleep a bit here because some games // do input detection by polling try { Thread.sleep(100); } catch (InterruptedException ignored) {} // Raise the mouse button conn.sendMouseButtonUp(buttonIndex); } } private synchronized void startDragTimer() { dragTimer = new Timer(true); dragTimer.schedule(new TimerTask() { @Override public void run() { synchronized (TouchContext.this) { // Check if someone already set move if (confirmedMove) { return; } // Check if someone cancelled us if (dragTimer == null) { return; } // Uncancellable now dragTimer = null; // We haven't been cancelled before the timer expired so begin dragging confirmedDrag = true; conn.sendMouseButtonDown(getMouseButtonIndex()); } } }, DRAG_TIME_THRESHOLD); } private synchronized void cancelDragTimer() { if (dragTimer != null) { dragTimer.cancel(); dragTimer = null; } } private synchronized void checkForConfirmedMove(int eventX, int eventY) { // If we've already confirmed something, get out now if (confirmedMove || confirmedDrag) { return; } // If it leaves the tap bounds before the drag time expires, it's a move. if (!isWithinTapBounds(eventX, eventY)) { confirmedMove = true; cancelDragTimer(); return; } // Check if we've exceeded the maximum distance moved distanceMoved += Math.sqrt(Math.pow(eventX - lastTouchX, 2) + Math.pow(eventY - lastTouchY, 2)); if (distanceMoved >= TAP_DISTANCE_THRESHOLD) { confirmedMove = true; cancelDragTimer(); return; } } public boolean touchMoveEvent(int eventX, int eventY) { if (eventX != lastTouchX || eventY != lastTouchY) { // We only send moves and drags for the primary touch point if (actionIndex == 0) { checkForConfirmedMove(eventX, eventY); int deltaX = eventX - lastTouchX; int deltaY = eventY - lastTouchY; // Scale the deltas based on the factors passed to our constructor deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor); deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor); // Fix up the signs if (eventX < lastTouchX) { deltaX = -deltaX; } if (eventY < lastTouchY) { deltaY = -deltaY; } // If the scaling factor ended up rounding deltas to zero, wait until they are // non-zero to update lastTouch that way devices that report small touch events often // will work correctly if (deltaX != 0) { lastTouchX = eventX; } if (deltaY != 0) { lastTouchY = eventY; } conn.sendMouseMove((short)deltaX, (short)deltaY); } else { lastTouchX = eventX; lastTouchY = eventY; } } return true; } public void cancelTouch() { cancelled = true; // Cancel the drag timer cancelDragTimer(); // If it was a confirmed drag, we'll need to raise the button now if (confirmedDrag) { conn.sendMouseButtonUp(getMouseButtonIndex()); } } public boolean isCancelled() { return cancelled; } }