Compare commits

..

47 Commits

Author SHA1 Message Date
Cameron Gutman 32af2d0831 Increment version code 2016-06-26 14:05:38 -07:00
Cameron Gutman 242b03d4b5 Add gradle.properties for Dex In-Process 2016-06-20 21:33:39 -07:00
Cameron Gutman 87a62666ac Prefer Shield capture provider over Android N 2016-06-20 20:43:59 -07:00
Cameron Gutman 2dcf5486da Revert "Display the running app first on the app grid"
This reverts commit 36f8cc02cb.
2016-06-20 20:30:47 -07:00
Cameron Gutman 60d3d8b3ae Version to 4.6 2016-06-18 15:17:15 -07:00
Cameron Gutman e9141d65fe Improve reliability of missing root detection 2016-06-18 14:54:53 -07:00
Cameron Gutman aae591daec Improve multi-window experience on N 2016-06-18 14:52:20 -07:00
Cameron Gutman a5ca8a7472 Add a hack to avoid crashing when the app window divider is dragged off of the screen on N multi-window 2016-06-18 14:40:42 -07:00
Cameron Gutman 36f8cc02cb Display the running app first on the app grid 2016-06-18 13:38:37 -07:00
Cameron Gutman 55b9645651 Fix minor Lint issues 2016-06-18 12:38:43 -07:00
Cameron Gutman d30ecbed5b Update gradle 2016-06-18 11:40:55 -07:00
Cameron Gutman 0bbd27f04c Update common jar 2016-06-18 11:37:00 -07:00
Cameron Gutman 3c53fb7403 Update target SDK to 24 2016-06-18 11:19:04 -07:00
Cameron Gutman 7a81950819 Enable sustained performance mode on N+ when streaming 2016-06-18 11:17:34 -07:00
Cameron Gutman 74f212c702 Add Android N mouse capture support 2016-06-18 11:15:53 -07:00
Cameron Gutman 36be943854 Add support for more Xbox controller models 2016-06-13 22:28:48 -05:00
Cameron Gutman 26a4fc75a5 Add handling for the ADT-1 controller 2016-06-13 21:28:54 -05:00
Cameron Gutman a5ec5fc265 Select the optimal display mode before streaming 2016-06-13 21:23:00 -05:00
Cameron Gutman 541ac44be4 Add an unified input capture interface 2016-06-13 20:33:43 -05:00
Cameron Gutman 117b555fcd Fix wiki link 2016-05-30 12:22:54 -05:00
Cameron Gutman a10cd04441 Clarify wording in H.265 settings 2016-05-29 16:39:20 -05:00
Cameron Gutman 53dccbde2a Repeat key down events are needed for proper key repeating 2016-05-29 15:52:18 -05:00
Cameron Gutman 56625dfe4b Bump version to 4.5.10 2016-05-21 18:28:02 -05:00
Cameron Gutman 2eab5a3b7b Update the ENet submodule to include the MTU fix for LTE streaming 2016-05-21 18:09:40 -05:00
Cameron Gutman f9e811862a Bump version to 4.5.9 2016-05-19 22:39:50 -04:00
Cameron Gutman 25ccc3d0e1 Fix for Xiaomi gamepad mapping 2016-05-19 22:31:14 -04:00
Cameron Gutman 8853bf0670 Bump version to 4.5.8 2016-05-07 21:25:11 -04:00
Cameron Gutman 71fa3a824b Update gradle 2016-05-07 21:20:05 -04:00
Cameron Gutman 56fd50834c Update common jar with the RTP queue changes 2016-05-07 21:19:54 -04:00
Cameron Gutman 48ba812cf6 When combining analog inputs, use the one with the highest magnitude 2016-05-07 21:19:02 -04:00
Cameron Gutman 019dc6d45f Display a warning at stream start if root access is unavailable 2016-05-07 20:59:17 -04:00
Cameron Gutman cbcb784a79 Blacklist Tegra X1's HEVC decoder until the correct SPS fixups are in place 2016-05-07 20:53:45 -04:00
Cameron Gutman 39fa0258ad Force the Archos Gamepad 2's controller buttons as controller 0 2016-04-23 22:23:43 -04:00
Cameron Gutman d0dd5bfa8c Combine all controllers with the same controller number before sending controller input 2016-04-23 22:23:01 -04:00
Cameron Gutman b948c47618 Increment patch level again 2016-04-22 00:15:21 -04:00
Cameron Gutman 18cae8ac53 Use common jar from the android branch (da297b5a89c2b645573f231af3e47752f27fbc79) to fix API 19 issues 2016-04-21 13:33:08 -04:00
Cameron Gutman 0576231dfc Update patch level to 4.5.7.1 2016-04-20 13:52:53 -04:00
Cameron Gutman 6ad35a83dd Update common jar with fix for < API 19 2016-04-20 13:42:57 -04:00
Cameron Gutman 33d4dfc745 Revert "Prevent the small-mode default from changing between portrait and landscape orientations"
This reverts commit 7c1eb80d62.
2016-04-20 13:34:24 -04:00
Cameron Gutman f3bf63a668 Increment app version 2016-04-19 20:49:40 -04:00
Cameron Gutman 2dbb7395a4 Restart the app view activity when configuration changes are made that could require the grid to be resized. This is much simpler than handling all of the fine edge cases here. 2016-04-19 20:38:05 -04:00
Cameron Gutman 7c1eb80d62 Prevent the small-mode default from changing between portrait and landscape orientations 2016-04-19 20:36:04 -04:00
Cameron Gutman f2bf093691 Update Gradle 2016-04-19 19:51:00 -04:00
Cameron Gutman 2f002bfa4a Fix being stuck in small-icon mode after resizing to minimum size on Android N 2016-04-19 19:36:10 -04:00
Cameron Gutman 4a19038d54 Update common jar to fix crashes in jnienet 2016-04-19 19:18:13 -04:00
Cameron Gutman 15fb3dd92c Fix mouse scaling to scale by stream view size rather than screen size for better behavior on N and in general 2016-04-19 19:13:57 -04:00
Cameron Gutman e0982d3961 Fix video stream aspect ratio scaling in multi-window mode on Android N 2016-04-19 18:40:45 -04:00
40 changed files with 623 additions and 257 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ whether in your own home or over the internet.
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for more detailed information or a troubleshooting guide.
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
##Features
+5 -5
View File
@@ -4,15 +4,15 @@ import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
targetSdkVersion 24
versionName "4.5.6"
versionCode = 95
versionName "4.6"
versionCode = 103
}
productFlavors {
Binary file not shown.
+19 -3
View File
@@ -96,9 +96,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
// Load the app grid with cached data (if possible)
populateAppGridWithCache();
getFragmentManager().beginTransaction()
.replace(R.id.appFragmentContainer, new AdapterFragment())
.commitAllowingStateLoss();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isFinishing() || isChangingConfigurations()) {
return;
}
// Despite my best efforts to catch all conditions that could
// cause the activity to be destroyed when we try to commit
// I haven't been able to, so we have this try-catch block.
try {
getFragmentManager().beginTransaction()
.replace(R.id.appFragmentContainer, new AdapterFragment())
.commitAllowingStateLoss();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
});
}
}.start();
}
+124 -92
View File
@@ -4,10 +4,10 @@ package com.limelight;
import com.limelight.binding.PlatformBinding;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.input.NvMouseHelper;
import com.limelight.binding.input.capture.InputCaptureManager;
import com.limelight.binding.input.capture.InputCaptureProvider;
import com.limelight.binding.input.TouchContext;
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.binding.input.evdev.EvdevHandler;
import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.video.EnhancedDecoderRenderer;
@@ -22,6 +22,7 @@ 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.ui.StreamView;
import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog;
@@ -38,6 +39,7 @@ import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -47,12 +49,10 @@ import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnGenericMotionListener;
import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -74,8 +74,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private final TouchContext[] touchContextMap = new TouchContext[2];
private long threeFingerDownTime = 0;
private static final double REFERENCE_HORIZ_RES = 1280;
private static final double REFERENCE_VERT_RES = 720;
private static final int REFERENCE_HORIZ_RES = 1280;
private static final int REFERENCE_VERT_RES = 720;
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
@@ -84,19 +84,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private KeyboardTranslator keybTranslator;
private PreferenceConfiguration prefConfig;
private final Point screenSize = new Point(0, 0);
private NvConnection conn;
private SpinnerDialog spinner;
private boolean displayedFailureDialog = false;
private boolean connecting = false;
private boolean connected = false;
private boolean deferredSurfaceResize = false;
private EvdevHandler evdevHandler;
private InputCaptureProvider inputCaptureProvider;
private int modifierFlags = 0;
private boolean grabbedInput = true;
private boolean grabComboDown = false;
private StreamView streamView;
private EnhancedDecoderRenderer decoderRenderer;
@@ -175,13 +174,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
drFlags |= VideoDecoderRenderer.FLAG_FILL_SCREEN;
}
Display display = getWindowManager().getDefaultDisplay();
display.getSize(screenSize);
// Listen for events on the game surface
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
sv.setOnGenericMotionListener(this);
sv.setOnTouchListener(this);
streamView = (StreamView) findViewById(R.id.surfaceView);
streamView.setOnGenericMotionListener(this);
streamView.setOnTouchListener(this);
// Warn the user if they're on a metered connection
checkDataConnection();
@@ -250,44 +246,24 @@ public class Game extends Activity implements SurfaceHolder.Callback,
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
inputManager.registerInputDeviceListener(controllerHandler, null);
boolean aspectRatioMatch = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
// setFixedSize so we can handle 4K properly. The only known devices that have
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
// on these devices. On Marshmallow, we can start changing to 4K manually but no
// 4K devices run 6.0 at the moment.
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
LimeLog.info("Stream has compatible aspect ratio with output display");
aspectRatioMatch = true;
}
}
SurfaceHolder sh = sv.getHolder();
if (prefConfig.stretchVideo || aspectRatioMatch) {
// Set the surface to the size of the video
sh.setFixedSize(prefConfig.width, prefConfig.height);
}
else {
deferredSurfaceResize = true;
}
// Set to the optimal mode for streaming
prepareDisplayForRendering();
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
touchContextMap[i] = new TouchContext(conn, i,
(REFERENCE_HORIZ_RES / (double)screenSize.x),
(REFERENCE_VERT_RES / (double)screenSize.y));
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
streamView);
}
if (LimelightBuildProps.ROOT_BUILD) {
// Start watching for raw input
evdevHandler = new EvdevHandler(this, this);
evdevHandler.start();
// Use sustained performance mode on N+ to ensure consistent
// CPU availability
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
getWindow().setSustainedPerformanceMode(true);
}
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
if (prefConfig.onscreenController) {
// create virtual onscreen controller
virtualController = new VirtualController(conn,
@@ -303,26 +279,95 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// The connection will be started when the surface gets created
sh.addCallback(this);
streamView.getHolder().addCallback(this);
}
private void resizeSurfaceWithAspectRatio(SurfaceView sv, double vidWidth, double vidHeight)
{
// Get the visible width of the activity
double visibleWidth = getWindow().getDecorView().getWidth();
private void prepareDisplayForRendering() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
ViewGroup.LayoutParams lp = sv.getLayoutParams();
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode();
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate() &&
candidate.getRefreshRate() < 63;
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
candidate.getPhysicalWidth() <= 4096;
// Calculate the new size of the SurfaceView
lp.width = (int) visibleWidth;
lp.height = (int) ((vidHeight / vidWidth) * visibleWidth);
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
// Apply the size change
sv.setLayoutParams(lp);
// On non-4K streams, we force the resolution to never change
if (prefConfig.width < 3840) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue;
}
}
// refresh virtual controller layout
if (virtualController != null) {
virtualController.refreshLayout();
// Make sure the refresh rate doesn't regress
if (!refreshRateOk) {
continue;
}
// Make sure the resolution doesn't regress
if (!resolutionOk) {
continue;
}
bestMode = candidate;
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
}
// On L, we can at least tell the OS that we want 60 Hz
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float bestRefreshRate = display.getRefreshRate();
for (float candidate : display.getSupportedRefreshRates()) {
if (candidate > bestRefreshRate && candidate < 63) {
LimeLog.info("Examining refresh rate: "+candidate);
bestRefreshRate = candidate;
}
}
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
}
// Apply the display mode change
getWindow().setAttributes(windowLayoutParams);
// From 4.4 to 5.1 we can't ask for a 4K display mode, so we'll
// need to hint the OS to provide one.
boolean aspectRatioMatch = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
// setFixedSize so we can handle 4K properly. The only known devices that have
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
// on these devices. On Marshmallow, we can start changing to 4K manually but no
// 4K devices run 6.0 at the moment.
Point screenSize = new Point(0, 0);
display.getSize(screenSize);
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
LimeLog.info("Stream has compatible aspect ratio with output display");
aspectRatioMatch = true;
}
}
if (prefConfig.stretchVideo || aspectRatioMatch) {
// Set the surface to the size of the video
streamView.getHolder().setFixedSize(prefConfig.width, prefConfig.height);
}
else {
// Set the surface to scale based on the aspect ratio of the stream
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
}
}
@@ -424,16 +469,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void run() {
if (grabbedInput) {
NvMouseHelper.setCursorVisibility(Game.this, true);
if (evdevHandler != null) {
evdevHandler.ungrabAll();
}
inputCaptureProvider.disableCapture();
}
else {
NvMouseHelper.setCursorVisibility(Game.this, false);
if (evdevHandler != null) {
evdevHandler.regrabAll();
}
inputCaptureProvider.enableCapture();
}
grabbedInput = !grabbedInput;
@@ -540,11 +579,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return true;
}
// Eat repeat down events
if (event.getRepeatCount() > 0) {
return true;
}
// Pass through keyboard input if we're not grabbing
if (!grabbedInput) {
return super.onKeyDown(keyCode, event);
@@ -661,10 +695,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// Get relative axis values if we can
if (NvMouseHelper.eventHasRelativeMouseAxes(event)) {
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
// Send the deltas straight from the motion event
conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event),
(short)NvMouseHelper.getRelativeAxisY(event));
conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event),
(short) inputCaptureProvider.getRelativeAxisY(event));
// We have to also update the position Android thinks the cursor is at
// in order to avoid jumping when we stop moving or click.
@@ -802,8 +836,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Scale the deltas if the device resolution is different
// than the stream resolution
deltaX = (int)Math.round((double)deltaX * (REFERENCE_HORIZ_RES / (double)screenSize.x));
deltaY = (int)Math.round((double)deltaY * (REFERENCE_VERT_RES / (double)screenSize.y));
deltaX = (int)Math.round((double)deltaX * (REFERENCE_HORIZ_RES / (double)streamView.getWidth()));
deltaY = (int)Math.round((double)deltaY * (REFERENCE_VERT_RES / (double)streamView.getHeight()));
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
@@ -841,14 +875,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
conn.stop();
}
// Close the Evdev reader to allow use of captured input devices
if (evdevHandler != null) {
evdevHandler.stop();
evdevHandler = null;
}
// Enable cursor visibility again
NvMouseHelper.setCursorVisibility(this, true);
inputCaptureProvider.disableCapture();
// Destroy the capture provider
inputCaptureProvider.destroy();
}
@Override
@@ -894,7 +925,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Hide the mouse cursor now. Doing it before
// dismissing the spinner seems to be undone
// when the spinner gets displayed.
NvMouseHelper.setCursorVisibility(Game.this, false);
inputCaptureProvider.enableCapture();
}
});
@@ -932,13 +963,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (!connected && !connecting) {
connecting = true;
// Resize the surface to match the aspect ratio of the video
// This must be done after the surface is created.
if (deferredSurfaceResize) {
resizeSurfaceWithAspectRatio((SurfaceView) findViewById(R.id.surfaceView),
prefConfig.width, prefConfig.height);
}
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
PlatformBinding.getAudioRenderer(), decoderRenderer);
}
@@ -947,6 +971,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (connected) {
// HACK: Android is supposed to let you return from this function
// before throwing a fit if you access the surface again. Unfortunately,
// MediaCodec often tries to access the destroyed surface and triggers
// an IllegalStateException. To workaround this, we will invoke
// the DecoderRenderer's stop function ourselves, so it will hopefully
// happen early enough to not trigger the bug
decoderRenderer.stop();
stopConnection();
}
}
+2 -2
View File
@@ -299,7 +299,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Stop updates and wait while pairing
stopComputerUpdates(true);
InetAddress addr = null;
InetAddress addr;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp;
}
@@ -429,7 +429,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
NvHTTP httpConn;
String message;
try {
InetAddress addr = null;
InetAddress addr;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp;
}
@@ -82,6 +82,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS;
defaultContext.controllerNumber = (short) 0;
defaultContext.assignedControllerNumber = true;
}
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
@@ -146,8 +147,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
InputDeviceContext devContext = (InputDeviceContext) context;
LimeLog.info(devContext.name+" ("+context.id+") needs a controller number assigned");
if (devContext.name != null && devContext.name.contains("gpio-keys")) {
// This is the back button on Shield portable consoles
if (devContext.name != null &&
(devContext.name.contains("gpio-keys") || // This is the back button on Shield portable consoles
devContext.name.contains("joy_key"))) { // These are the gamepad buttons on the Archos Gamepad 2
LimeLog.info("Built-in buttons hardcoded as controller 0");
context.controllerNumber = 0;
}
@@ -212,6 +214,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
String devName = dev.getName();
LimeLog.info("Creating controller context for device: "+devName);
LimeLog.info(dev.toString());
context.name = devName;
context.id = dev.getId();
@@ -229,6 +232,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
InputDevice.MotionRange throttleRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_THROTTLE);
if (leftTriggerRange != null && rightTriggerRange != null)
{
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
@@ -241,6 +245,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
context.rightTriggerAxis = MotionEvent.AXIS_GAS;
}
else if (brakeRange != null && throttleRange != null)
{
// Others use THROTTLE and BRAKE (like Xiaomi)
context.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
context.rightTriggerAxis = MotionEvent.AXIS_THROTTLE;
}
else
{
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
@@ -324,8 +334,22 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
boolean[] hasSelectKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
if (hasSelectKey[0] && hasSelectKey[1]) {
LimeLog.info("Ignoring back button because select is present");
context.ignoreBack = true;
// Xiaomi gamepads claim to have both buttons then only send KEYCODE_BACK events
if (dev.getVendorId() != 0x2717) {
LimeLog.info("Ignoring back button because select is present");
context.ignoreBack = true;
}
}
}
// The ADT-1 controller needs a similar fixup to the ASUS Gamepad
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
// The device name provided is just "Gamepad" which is pretty useless, so we
// use VID/PID instead
if (dev.getVendorId() == 0x18d1 && dev.getProductId() == 0x2c40) {
context.backIsStart = true;
context.modeIsSelect = true;
context.triggerDeadzone = 0.30f;
}
}
@@ -398,12 +422,82 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return context;
}
private void sendControllerInputPacket(GenericControllerContext context) {
assignControllerNumberIfNeeded(context);
conn.sendControllerInput(context.controllerNumber, context.inputMap,
context.leftTrigger, context.rightTrigger,
context.leftStickX, context.leftStickY,
context.rightStickX, context.rightStickY);
private byte maxByMagnitude(byte a, byte b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
if (absA > absB) {
return a;
}
else {
return b;
}
}
private short maxByMagnitude(short a, short b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
if (absA > absB) {
return a;
}
else {
return b;
}
}
private void sendControllerInputPacket(GenericControllerContext originalContext) {
assignControllerNumberIfNeeded(originalContext);
// Take the context's controller number and fuse all inputs with the same number
short controllerNumber = originalContext.controllerNumber;
short inputMap = 0;
byte leftTrigger = 0;
byte rightTrigger = 0;
short leftStickX = 0;
short leftStickY = 0;
short rightStickX = 0;
short rightStickY = 0;
// In order to properly handle controllers that are split into multiple devices,
// we must aggregate all controllers with the same controller number into a single
// device before we send it.
for (int i = 0; i < inputDeviceContexts.size(); i++) {
GenericControllerContext context = inputDeviceContexts.valueAt(i);
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
inputMap |= context.inputMap;
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
leftStickX |= maxByMagnitude(leftStickX, context.leftStickX);
leftStickY |= maxByMagnitude(leftStickY, context.leftStickY);
rightStickX |= maxByMagnitude(rightStickX, context.rightStickX);
rightStickY |= maxByMagnitude(rightStickY, context.rightStickY);
}
}
for (int i = 0; i < usbDeviceContexts.size(); i++) {
GenericControllerContext context = usbDeviceContexts.valueAt(i);
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
inputMap |= context.inputMap;
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
leftStickX |= maxByMagnitude(leftStickX, context.leftStickX);
leftStickY |= maxByMagnitude(leftStickY, context.leftStickY);
rightStickX |= maxByMagnitude(rightStickX, context.rightStickX);
rightStickY |= maxByMagnitude(rightStickY, context.rightStickY);
}
}
if (defaultContext.controllerNumber == controllerNumber) {
inputMap |= defaultContext.inputMap;
leftTrigger |= maxByMagnitude(leftTrigger, defaultContext.leftTrigger);
rightTrigger |= maxByMagnitude(rightTrigger, defaultContext.rightTrigger);
leftStickX |= maxByMagnitude(leftStickX, defaultContext.leftStickX);
leftStickY |= maxByMagnitude(leftStickY, defaultContext.leftStickY);
rightStickX |= maxByMagnitude(rightStickX, defaultContext.rightStickX);
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
}
conn.sendControllerInput(controllerNumber, inputMap,
leftTrigger, rightTrigger,
leftStickX, leftStickY,
rightStickX, rightStickY);
}
// Return a valid keycode, 0 to consume, or -1 to not consume the event
@@ -1,5 +1,7 @@
package com.limelight.binding.input;
import android.view.View;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket;
@@ -17,23 +19,27 @@ public class TouchContext {
private boolean confirmedDrag;
private Timer dragTimer;
private double distanceMoved;
private double xFactor, yFactor;
private final NvConnection conn;
private final int actionIndex;
private final double xFactor;
private final double yFactor;
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, double xFactor, double yFactor)
public TouchContext(NvConnection conn, int actionIndex,
int referenceWidth, int referenceHeight, View view)
{
this.conn = conn;
this.actionIndex = actionIndex;
this.xFactor = xFactor;
this.yFactor = yFactor;
this.referenceWidth = referenceWidth;
this.referenceHeight = referenceHeight;
this.targetView = view;
}
public int getActionIndex()
@@ -68,6 +74,10 @@ public class TouchContext {
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();
@@ -0,0 +1,59 @@
package com.limelight.binding.input.capture;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
import android.view.ViewGroup;
@TargetApi(Build.VERSION_CODES.N)
public class AndroidCaptureProvider extends InputCaptureProvider {
private ViewGroup rootViewGroup;
private Context context;
public AndroidCaptureProvider(Activity activity) {
this.context = activity;
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
}
public static boolean isCaptureProviderSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
private void setPointerIconOnAllViews(PointerIcon icon) {
for (int i = 0; i < rootViewGroup.getChildCount(); i++) {
View view = rootViewGroup.getChildAt(i);
view.setPointerIcon(icon);
}
rootViewGroup.setPointerIcon(icon);
}
@Override
public void enableCapture() {
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
}
@Override
public void disableCapture() {
setPointerIconOnAllViews(null);
}
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X) != 0 ||
event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y) != 0;
}
@Override
public float getRelativeAxisX(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
}
@Override
public float getRelativeAxisY(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
}
}
@@ -0,0 +1,31 @@
package com.limelight.binding.input.capture;
import android.app.Activity;
import com.limelight.LimeLog;
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevListener;
public class InputCaptureManager {
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
// Shield capture is preferred because it can capture when the cursor is over
// the system UI. Android N native capture can only capture over views owned
// by the application.
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Android N+ native mouse capture");
return new AndroidCaptureProvider(activity);
}
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Evdev mouse capture");
return new EvdevCaptureProvider(activity, rootListener);
}
else {
LimeLog.info("Mouse capture not available");
return new NullCaptureProvider();
}
}
}
@@ -0,0 +1,21 @@
package com.limelight.binding.input.capture;
import android.view.MotionEvent;
public abstract class InputCaptureProvider {
public abstract void enableCapture();
public abstract void disableCapture();
public void destroy() {}
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return false;
}
public float getRelativeAxisX(MotionEvent event) {
return 0;
}
public float getRelativeAxisY(MotionEvent event) {
return 0;
}
}
@@ -0,0 +1,10 @@
package com.limelight.binding.input.capture;
public class NullCaptureProvider extends InputCaptureProvider {
@Override
public void enableCapture() {}
@Override
public void disableCapture() {}
}
@@ -1,12 +1,10 @@
package com.limelight.binding.input;
package com.limelight.binding.input.capture;
import android.content.Context;
import android.hardware.input.InputManager;
import android.view.MotionEvent;
import com.limelight.LimeLog;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -18,12 +16,14 @@ import java.lang.reflect.Method;
//
// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm
public class NvMouseHelper {
public class ShieldCaptureProvider extends InputCaptureProvider {
private static boolean nvExtensionSupported;
private static Method methodSetCursorVisibility;
private static int AXIS_RELATIVE_X;
private static int AXIS_RELATIVE_Y;
private Context context;
static {
try {
methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
@@ -36,16 +36,19 @@ public class NvMouseHelper {
nvExtensionSupported = true;
} catch (Exception e) {
LimeLog.info("NvMouseHelper not supported");
nvExtensionSupported = false;
}
}
public static boolean setCursorVisibility(Context context, boolean visible) {
if (!nvExtensionSupported) {
return false;
}
public ShieldCaptureProvider(Context context) {
this.context = context;
}
public static boolean isCaptureProviderSupported() {
return nvExtensionSupported;
}
private boolean setCursorVisibility(boolean visible) {
try {
methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible);
return true;
@@ -58,19 +61,29 @@ public class NvMouseHelper {
return false;
}
public static boolean eventHasRelativeMouseAxes(MotionEvent event) {
if (!nvExtensionSupported) {
return false;
}
@Override
public void enableCapture() {
setCursorVisibility(false);
}
@Override
public void disableCapture() {
setCursorVisibility(true);
}
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
event.getAxisValue(AXIS_RELATIVE_Y) != 0;
}
public static float getRelativeAxisX(MotionEvent event) {
@Override
public float getRelativeAxisX(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X);
}
public static float getRelativeAxisY(MotionEvent event) {
@Override
public float getRelativeAxisY(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_Y);
}
}
@@ -1,13 +1,5 @@
package com.limelight.binding.input.driver;
import android.hardware.usb.UsbEndpoint;
import com.limelight.LimeLog;
import com.limelight.binding.video.MediaCodecHelper;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public abstract class AbstractController {
private final int deviceId;
@@ -72,14 +72,14 @@ public class UsbDriverService extends Service implements UsbDriverListener {
// Initial attachment broadcast
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// Continue the state machine
handleUsbDeviceState(device);
}
// Subsequent permission dialog completion intent
else if (action.equals(ACTION_USB_PERMISSION)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// If we got this far, we've already found we're able to handle this device
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
@@ -10,56 +10,38 @@ import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer;
public class Xbox360Controller extends AbstractXboxController {
private static final int XB360_IFACE_SUBCLASS = 93;
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
// This list is taken from the Xpad driver in the Linux kernel.
// I've excluded the devices that aren't "controllers" in the traditional sense, but
// if people really want to use their dancepads or fight sticks with Moonlight, I can
// put them in.
private static final DeviceIdTuple[] supportedDeviceTuples = {
new DeviceIdTuple(0x045e, 0x028e, "Microsoft X-Box 360 pad"),
new DeviceIdTuple(0x044f, 0xb326, "Thrustmaster Gamepad GP XID"),
new DeviceIdTuple(0x046d, 0xc21d, "Logitech Gamepad F310"),
new DeviceIdTuple(0x046d, 0xc21e, "Logitech Gamepad F510"),
new DeviceIdTuple(0x046d, 0xc21f, "Logitech Gamepad F710"),
new DeviceIdTuple(0x046d, 0xc242, "Logitech Chillstream Controller"),
new DeviceIdTuple(0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller"),
new DeviceIdTuple(0x0738, 0x4726, "Mad Catz Xbox 360 Controller"),
new DeviceIdTuple(0x0738, 0xb726, "Mad Catz Xbox controller - MW2"),
new DeviceIdTuple(0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad"),
new DeviceIdTuple(0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360"),
new DeviceIdTuple(0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller"),
new DeviceIdTuple(0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0301, "Logic3 Controller"),
new DeviceIdTuple(0x0e6f, 0x0401, "Logic3 Controller"),
new DeviceIdTuple(0x12ab, 0x0301, "PDP AFTERGLOW AX.1"),
new DeviceIdTuple(0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller"),
new DeviceIdTuple(0x1532, 0x0037, "Razer Sabertooth"),
new DeviceIdTuple(0x15e4, 0x3f00, "Power A Mini Pro Elite"),
new DeviceIdTuple(0x15e4, 0x3f0a, "Xbox Airflo wired controller"),
new DeviceIdTuple(0x15e4, 0x3f10, "Batarang Xbox 360 controller"),
new DeviceIdTuple(0x162e, 0xbeef, "Joytech Neo-Se Take2"),
new DeviceIdTuple(0x1689, 0xfd00, "Razer Onza Tournament Edition"),
new DeviceIdTuple(0x1689, 0xfd01, "Razer Onza Classic Edition"),
new DeviceIdTuple(0x24c6, 0x5d04, "Razer Sabertooth"),
new DeviceIdTuple(0x1bad, 0xf016, "Mad Catz Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)"),
new DeviceIdTuple(0x1bad, 0xf900, "Harmonix Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf901, "Gamestop Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf903, "Tron Xbox 360 controller"),
new DeviceIdTuple(0x24c6, 0x5300, "PowerA MINI PROEX Controller"),
new DeviceIdTuple(0x24c6, 0x5303, "Xbox Airflo wired controller"),
private static final int[] SUPPORTED_VENDORS = {
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
0x0738, // Mad Catz
0x0e6f, // Unknown
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
0x1bad, // Harmonix
0x0f0d, // Hori
0x1689, // Razer Onza
0x24c6, // PowerA
0x1532, // Razer Sabertooth
0x15e4, // Numark
0x162e, // Joytech
};
public static boolean canClaimDevice(UsbDevice device) {
for (DeviceIdTuple tuple : supportedDeviceTuples) {
if (device.getVendorId() == tuple.vid && device.getProductId() == tuple.pid) {
LimeLog.info("XB360 can claim device: " + tuple.name);
for (int supportedVid : SUPPORTED_VENDORS) {
if (device.getVendorId() == supportedVid &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB360_IFACE_PROTOCOL) {
return true;
}
}
return false;
}
@@ -151,16 +133,4 @@ public class Xbox360Controller extends AbstractXboxController {
// No need to fail init if the LED command fails
return true;
}
private static class DeviceIdTuple {
public final int vid;
public final int pid;
public final String name;
public DeviceIdTuple(int vid, int pid, String name) {
this.vid = vid;
this.pid = pid;
this.name = name;
}
}
}
@@ -3,22 +3,25 @@ package com.limelight.binding.input.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import com.limelight.LimeLog;
import com.limelight.binding.video.MediaCodecHelper;
import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class XboxOneController extends AbstractXboxController {
private static final int MICROSOFT_VID = 0x045e;
private static final int XB1_IFACE_SUBCLASS = 71;
private static final int XB1_IFACE_PROTOCOL = 208;
private static final int[] SUPPORTED_VENDORS = {
0x045e, // Microsoft
0x0738, // Mad Catz
0x0e6f, // Unknown
0x0f0d, // Hori
0x24c6, // PowerA
};
// FIXME: odata_serial
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
@@ -78,11 +81,17 @@ public class XboxOneController extends AbstractXboxController {
}
public static boolean canClaimDevice(UsbDevice device) {
return device.getVendorId() == MICROSOFT_VID &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
for (int supportedVid : SUPPORTED_VENDORS) {
if (device.getVendorId() == supportedVid &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
return true;
}
}
return false;
}
@Override
@@ -1,8 +1,11 @@
package com.limelight.binding.input.evdev;
import android.content.Context;
import android.app.Activity;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.binding.input.capture.InputCaptureProvider;
import java.io.DataOutputStream;
import java.io.File;
@@ -12,7 +15,7 @@ import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class EvdevHandler {
public class EvdevCaptureProvider extends InputCaptureProvider {
private final EvdevListener listener;
private final String libraryPath;
@@ -23,6 +26,8 @@ public class EvdevHandler {
private Process su;
private ServerSocket servSock;
private Socket evdevSock;
private Activity activity;
private boolean started = false;
private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2;
@@ -49,6 +54,7 @@ public class EvdevHandler {
try {
su = builder.start();
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
@@ -58,6 +64,7 @@ public class EvdevHandler {
try {
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
@@ -155,23 +162,48 @@ public class EvdevHandler {
}
};
public EvdevHandler(Context context, EvdevListener listener) {
public EvdevCaptureProvider(Activity activity, EvdevListener listener) {
this.listener = listener;
this.libraryPath = context.getApplicationInfo().nativeLibraryDir;
this.activity = activity;
this.libraryPath = activity.getApplicationInfo().nativeLibraryDir;
}
public void regrabAll() {
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
}
private void reportDeviceNotRooted() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
}
});
}
@Override
public void enableCapture() {
if (!started) {
// Start the handler thread if it's our first time
// capturing
handlerThread.start();
started = true;
}
else {
// Send a request to regrab if we're already capturing
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void ungrabAll() {
if (!shutdown && evdevOut != null) {
@Override
public void disableCapture() {
if (started && !shutdown && evdevOut != null) {
try {
evdevOut.write(UNGRAB_REQUEST);
} catch (IOException e) {
@@ -180,15 +212,16 @@ public class EvdevHandler {
}
}
public void start() {
handlerThread.start();
}
public void stop() {
@Override
public void destroy() {
// We need to stop the process in this context otherwise
// we could get stuck waiting on output from the process
// in order to terminate it.
if (!started) {
return;
}
shutdown = true;
handlerThread.interrupt();
@@ -52,7 +52,7 @@ public class DigitalButton extends VirtualControllerElement {
}
}
private List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>();
private List<DigitalButtonListener> listeners = new ArrayList<>();
private String text = "";
private int icon = -1;
private long timerLongClickTimeout = 3000;
@@ -20,7 +20,7 @@ public class DigitalPad extends VirtualControllerElement {
public final static int DIGITAL_PAD_DIRECTION_UP = 2;
public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4;
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8;
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>();
List<DigitalPadListener> listeners = new ArrayList<>();
private static final int DPAD_MARGIN = 5;
@@ -48,7 +48,7 @@ public class VirtualController {
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
private Button buttonConfigure = null;
private List<VirtualControllerElement> elements = new ArrayList<VirtualControllerElement>();
private List<VirtualControllerElement> elements = new ArrayList<>();
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) {
this.connection = conn;
@@ -475,8 +475,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} catch (InterruptedException ignored) { }
}
// Stop the decoder
videoDecoder.stop();
// We could stop the decoder here, but it seems to cause some problems
// so we'll just let release take care of it.
}
@Override
@@ -34,7 +34,7 @@ public class MediaCodecHelper {
private static final List<String> whitelistedHevcDecoders;
static {
directSubmitPrefixes = new LinkedList<String>();
directSubmitPrefixes = new LinkedList<>();
// These decoders have low enough input buffer latency that they
// can be directly invoked from the receive thread
@@ -48,33 +48,38 @@ public class MediaCodecHelper {
}
static {
preferredDecoders = new LinkedList<String>();
preferredDecoders = new LinkedList<>();
}
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
blacklistedDecoderPrefixes = new LinkedList<>();
// Software decoders that don't support H264 high profile
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
// decoder. While not strictly necessary, I'm going to fully blacklist this
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
blacklistedDecoderPrefixes.add("OMX.Nvidia.h265.decode");
}
static {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList<String>();
baselineProfileHackPrefixes = new LinkedList<>();
baselineProfileHackPrefixes.add("omx.intel");
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
whitelistedAdaptiveResolutionPrefixes = new LinkedList<>();
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
constrainedHighProfilePrefixes = new LinkedList<String>();
constrainedHighProfilePrefixes = new LinkedList<>();
constrainedHighProfilePrefixes.add("omx.intel");
}
@@ -213,7 +218,7 @@ public class MediaCodecHelper {
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
LinkedList<MediaCodecInfo> infoList = new LinkedList<MediaCodecInfo>();
LinkedList<MediaCodecInfo> infoList = new LinkedList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
@@ -371,7 +376,7 @@ public class MediaCodecHelper {
break;
cpuInfo.append((char)ch);
}
return cpuInfo.toString();
} finally {
br.close();
@@ -68,7 +68,7 @@ public class ComputerDatabaseManager {
public List<ComputerDetails> getAllComputers() {
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
LinkedList<ComputerDetails> computerList = new LinkedList<>();
while (c.moveToNext()) {
ComputerDetails details = new ComputerDetails();
@@ -45,7 +45,7 @@ public class ComputerManagerService extends Service {
private final AtomicInteger dbRefCount = new AtomicInteger(0);
private IdentityManager idManager;
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<>();
private ComputerManagerListener listener = null;
private final AtomicInteger activePolls = new AtomicInteger(0);
private boolean pollingActive = false;
@@ -14,8 +14,6 @@ import com.limelight.grid.assets.MemoryAssetLoader;
import com.limelight.grid.assets.NetworkAssetLoader;
import com.limelight.nvstream.http.ComputerDetails;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Comparator;
@@ -16,7 +16,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
protected final Context context;
protected final int defaultImageRes;
protected final int layoutId;
protected final ArrayList<T> itemList = new ArrayList<T>();
protected final ArrayList<T> itemList = new ArrayList<>();
protected final LayoutInflater inflater;
public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) {
@@ -135,7 +135,7 @@ public class CachedAppAssetLoader {
private LoaderTuple tuple;
public LoaderTask(ImageView imageView, boolean diskOnly) {
this.imageViewRef = new WeakReference<ImageView>(imageView);
this.imageViewRef = new WeakReference<>(imageView);
this.diskOnly = diskOnly;
}
@@ -213,7 +213,7 @@ public class CachedAppAssetLoader {
public AsyncDrawable(Resources res, Bitmap bitmap,
LoaderTask loaderTask) {
super(res, bitmap);
loaderTaskReference = new WeakReference<LoaderTask>(loaderTask);
loaderTaskReference = new WeakReference<>(loaderTask);
}
public LoaderTask getLoaderTask() {
@@ -7,7 +7,6 @@ import com.limelight.LimeLog;
import com.limelight.utils.CacheHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -29,7 +29,7 @@ import android.widget.Toast;
public class AddComputerManually extends Activity {
private TextView hostText;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<>();
private Thread addThread;
private final ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, final IBinder binder) {
@@ -3,6 +3,7 @@ package com.limelight.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.preference.PreferenceManager;
@@ -100,7 +101,7 @@ public class PreferenceConfiguration {
}
// Use small mode on anything smaller than a 7" tablet
return context.getResources().getConfiguration().smallestScreenWidthDp < 600;
return context.getResources().getConfiguration().smallestScreenWidthDp < 500;
}
public static int getDefaultBitrate(Context context) {
@@ -2,7 +2,6 @@ package com.limelight.preferences;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.app.Activity;
@@ -0,0 +1,55 @@
package com.limelight.ui;
import android.annotation.TargetApi;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;
public class StreamView extends SurfaceView {
private double desiredAspectRatio;
public void setDesiredAspectRatio(double aspectRatio) {
this.desiredAspectRatio = aspectRatio;
}
public StreamView(Context context) {
super(context);
}
public StreamView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StreamView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public StreamView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If no fixed aspect ratio has been provided, simply use the default onMeasure() behavior
if (desiredAspectRatio == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
// Based on code from: https://www.buzzingandroid.com/2012/11/easy-measuring-of-custom-views-with-specific-aspect-ratio/
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int measuredHeight, measuredWidth;
if (widthSize > heightSize * desiredAspectRatio) {
measuredHeight = heightSize;
measuredWidth = (int)(measuredHeight * desiredAspectRatio);
} else {
measuredWidth = widthSize;
measuredHeight = (int)(measuredWidth / desiredAspectRatio);
}
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
@@ -14,7 +14,7 @@ public class Dialog implements Runnable {
private AlertDialog alert;
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<>();
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
@@ -15,7 +15,7 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
private ProgressDialog progress;
private final boolean finish;
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<>();
private SpinnerDialog(Activity activity, String title, String message, boolean finish)
{
+1 -1
View File
@@ -4,7 +4,7 @@
android:layout_height="match_parent"
tools:context=".Game" >
<SurfaceView
<com.limelight.ui.StreamView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
+2 -2
View File
@@ -40,8 +40,8 @@
</string-array>
<string-array name="video_format_names">
<item>Use H.265 only if safe</item>
<item>Always use H.265 if available</item>
<item>Use H.265 only if stable</item>
<item>Always use H.265 (may crash)</item>
<item>Never use H.265</item>
</string-array>
<string-array name="video_format_values" translatable="false">
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
+19
View File
@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx3072m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true