Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b53f9a6a | |||
| 8da563b280 | |||
| d5b950e5cf | |||
| c46b9acf6b | |||
| d8e322bac9 | |||
| 44871626cf | |||
| f661522b5d | |||
| a454b0ab78 | |||
| 75bf84d0d9 | |||
| c248994ed4 | |||
| a7a34ec629 | |||
| 8d469c5d0a | |||
| e6979d50b5 | |||
| 6e25b135a3 | |||
| 04e093a2c2 | |||
| 813f2edd95 | |||
| 337d753a33 | |||
| 1137c74f76 | |||
| 0c1451f757 | |||
| 5ab9ea48fd | |||
| ffcb623040 | |||
| bfe6929642 | |||
| 50d45011a8 | |||
| 2f7087d6d3 | |||
| 92b71588d0 | |||
| 4f3d018764 | |||
| a22e33eeb9 | |||
| 6a939e7495 | |||
| f8ba7cf190 | |||
| d1e135db4d | |||
| 61a17afe69 | |||
| 47fd691884 | |||
| 0d171c6b28 | |||
| f0c69d08b8 | |||
| 629bf5766d | |||
| 233bceeece | |||
| 6660ea7d91 | |||
| 4864b2ca45 | |||
| 92097b318d | |||
| 997898c99d | |||
| 1174e03885 | |||
| ff0f54d541 | |||
| 814964a100 | |||
| 7e154292a9 | |||
| 0f9cba1053 | |||
| a4e134589d | |||
| cd80a94f28 | |||
| 57c645a291 |
+2
-2
@@ -8,8 +8,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
|
||||
versionName "6.1"
|
||||
versionCode = 182
|
||||
versionName "7.3"
|
||||
versionCode = 193
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
|
||||
Vendored
+1
@@ -25,3 +25,4 @@
|
||||
|
||||
# jMDNS
|
||||
-dontwarn javax.jmdns.impl.DNSCache
|
||||
-dontwarn org.slf4j.**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.limelight;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.limelight.computers.ComputerManagerListener;
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
@@ -28,7 +28,6 @@ import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@@ -45,6 +44,8 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
private AppGridAdapter appGridAdapter;
|
||||
private String uuidString;
|
||||
@@ -82,7 +83,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
localBinder.waitForReady();
|
||||
|
||||
// Get the computer object
|
||||
computer = localBinder.getComputer(UUID.fromString(uuidString));
|
||||
computer = localBinder.getComputer(uuidString);
|
||||
if (computer == null) {
|
||||
finish();
|
||||
return;
|
||||
@@ -156,7 +157,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
// Don't care about other computers
|
||||
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
|
||||
if (!details.uuid.equalsIgnoreCase(uuidString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,7 +181,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
@Override
|
||||
public void run() {
|
||||
// Disable shortcuts referencing this PC for now
|
||||
shortcutHelper.disableShortcut(details.uuid.toString(),
|
||||
shortcutHelper.disableShortcut(details.uuid,
|
||||
getResources().getString(R.string.scut_not_paired));
|
||||
|
||||
// Display a toast to the user and quit the activity
|
||||
@@ -216,7 +217,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
blockingLoadSpinner.dismiss();
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -280,7 +283,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
|
||||
updateUiWithAppList(applist);
|
||||
LimeLog.info("Loaded applist from cache");
|
||||
} catch (Exception e) {
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
if (lastRawApplist != null) {
|
||||
LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -66,12 +66,14 @@ import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@@ -110,6 +112,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean grabbedInput = true;
|
||||
private boolean grabComboDown = false;
|
||||
private StreamView streamView;
|
||||
private TextView notificationOverlayView;
|
||||
|
||||
private ShortcutHelper shortcutHelper;
|
||||
|
||||
@@ -201,6 +204,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
streamView.setOnTouchListener(this);
|
||||
streamView.setInputCallbacks(this);
|
||||
|
||||
notificationOverlayView = findViewById(R.id.notificationOverlay);
|
||||
|
||||
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -424,7 +429,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (prefConfig.onscreenController) {
|
||||
// create virtual onscreen controller
|
||||
virtualController = new VirtualController(conn,
|
||||
virtualController = new VirtualController(controllerHandler,
|
||||
(FrameLayout)streamView.getParent(),
|
||||
this);
|
||||
virtualController.refreshLayout();
|
||||
@@ -908,7 +913,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState(event));
|
||||
byte modifiers = getModifierState(event);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_DOWN, modifiers);
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -959,7 +969,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||
byte modifiers = getModifierState(event);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1255,6 +1272,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (connecting || connected) {
|
||||
connecting = connected = false;
|
||||
|
||||
controllerHandler.stop();
|
||||
|
||||
// Stop may take a few hundred ms to do some network I/O to tell
|
||||
// the server we're going away and clean up. Let it run in a separate
|
||||
// thread to keep things smooth for the UI. Inside moonlight-common,
|
||||
@@ -1310,8 +1329,41 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
LimeLog.severe("Connection terminated: " + errorCode);
|
||||
stopConnection();
|
||||
|
||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||
getResources().getString(R.string.conn_terminated_msg), true);
|
||||
// Display the error dialog if it was an unexpected termination.
|
||||
// Otherwise, just finish the activity immediately.
|
||||
if (errorCode != 0) {
|
||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||
getResources().getString(R.string.conn_terminated_msg), true);
|
||||
}
|
||||
else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionStatusUpdate(final int connectionStatus) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (prefConfig.disableWarnings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionStatus == MoonBridge.CONN_STATUS_POOR) {
|
||||
if (prefConfig.bitrate > 5000) {
|
||||
notificationOverlayView.setText(getResources().getString(R.string.slow_connection_msg));
|
||||
}
|
||||
else {
|
||||
notificationOverlayView.setText(getResources().getString(R.string.poor_connection_msg));
|
||||
}
|
||||
|
||||
notificationOverlayView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else if (connectionStatus == MoonBridge.CONN_STATUS_OKAY) {
|
||||
notificationOverlayView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1365,6 +1417,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor) {
|
||||
LimeLog.info(String.format((Locale)null, "Rumble on gamepad %d: %04x %04x", controllerNumber, lowFreqMotor, highFreqMotor));
|
||||
|
||||
controllerHandler.handleRumble(controllerNumber, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
if (!surfaceCreated) {
|
||||
|
||||
@@ -53,6 +53,8 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
@@ -420,7 +422,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
e.printStackTrace();
|
||||
message = e.getMessage();
|
||||
}
|
||||
@@ -521,8 +523,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
message = e.getMessage();
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
@@ -548,7 +551,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
Intent i = new Intent(this, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@@ -631,7 +634,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
if (details.equals(computer.details)) {
|
||||
// Disable or delete shortcuts referencing this PC
|
||||
shortcutHelper.disableShortcut(details.uuid.toString(),
|
||||
shortcutHelper.disableShortcut(details.uuid,
|
||||
getResources().getString(R.string.scut_deleted_pc));
|
||||
|
||||
pcGridAdapter.removeComputer(computer);
|
||||
@@ -662,7 +665,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
// Add a launcher shortcut for this PC
|
||||
if (details.pairState == PairState.PAIRED) {
|
||||
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false);
|
||||
shortcutHelper.createAppViewShortcut(details.uuid, details, false);
|
||||
}
|
||||
|
||||
if (existingEntry != null) {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ShortcutTrampoline extends Activity {
|
||||
managerBinder = localBinder;
|
||||
|
||||
// Get the computer object
|
||||
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
||||
computer = managerBinder.getComputer(uuidString);
|
||||
|
||||
if (computer == null) {
|
||||
Dialog.displayDialog(ShortcutTrampoline.this,
|
||||
@@ -77,7 +77,7 @@ public class ShortcutTrampoline extends Activity {
|
||||
@Override
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
// Don't care about other computers
|
||||
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
|
||||
if (!details.uuid.equalsIgnoreCase(uuidString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,14 @@ public class ShortcutTrampoline extends Activity {
|
||||
|
||||
protected boolean validateInput() {
|
||||
// Validate UUID
|
||||
if (uuidString == null) {
|
||||
Dialog.displayDialog(ShortcutTrampoline.this,
|
||||
getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.scut_invalid_uuid),
|
||||
true);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
UUID.fromString(uuidString);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
|
||||
@@ -168,8 +168,17 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playDecodedAudio(byte[] audioData) {
|
||||
track.write(audioData, 0, audioData.length);
|
||||
public void playDecodedAudio(short[] audioData) {
|
||||
// Only queue up to 40 ms of pending audio data in addition to what AudioTrack is buffering for us.
|
||||
if (MoonBridge.getPendingAudioFrames() < 8) {
|
||||
// This will block until the write is completed. That can cause a backlog
|
||||
// of pending audio data, so we do the above check to be able to bound
|
||||
// latency at 40 ms in that situation.
|
||||
track.write(audioData, 0, audioData.length);
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Too many pending audio frames: " + MoonBridge.getPendingAudioFrames());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
@@ -155,7 +154,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
} catch (Exception e) {
|
||||
// Nothing should go wrong here
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
LimeLog.info("Generated a new key pair");
|
||||
|
||||
@@ -4,8 +4,11 @@ import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
@@ -14,6 +17,7 @@ import android.view.MotionEvent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.input.driver.AbstractController;
|
||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||
import com.limelight.binding.input.driver.UsbDriverService;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
@@ -51,6 +55,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final double stickDeadzone;
|
||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||
private final GameGestures gestures;
|
||||
private final Vibrator deviceVibrator;
|
||||
private boolean hasGameController;
|
||||
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
@@ -61,10 +66,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
this.conn = conn;
|
||||
this.gestures = gestures;
|
||||
this.prefConfig = prefConfig;
|
||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone
|
||||
// HACK: For now we're hardcoding a 7% deadzone. Some deadzone
|
||||
// is required for controller batching support to work.
|
||||
int deadzonePercentage = 10;
|
||||
int deadzonePercentage = 7;
|
||||
|
||||
int[] ids = InputDevice.getDeviceIds();
|
||||
for (int id : ids) {
|
||||
@@ -151,6 +157,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
onInputDeviceAdded(deviceId);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
|
||||
|
||||
if (deviceContext.vibrator != null) {
|
||||
deviceContext.vibrator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
deviceVibrator.cancel();
|
||||
}
|
||||
|
||||
private static boolean hasJoystickAxes(InputDevice device) {
|
||||
return (device.getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK &&
|
||||
getMotionRangeForJoystickAxis(device, MotionEvent.AXIS_X) != null &&
|
||||
@@ -206,6 +224,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
if (PreferenceConfiguration.readPreferences(context).onscreenController) {
|
||||
LimeLog.info("Counting OSC gamepad");
|
||||
mask |= 1;
|
||||
}
|
||||
|
||||
LimeLog.info("Enumerated "+count+" gamepads");
|
||||
return mask;
|
||||
}
|
||||
@@ -298,10 +321,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.assignedControllerNumber = true;
|
||||
}
|
||||
|
||||
private UsbDeviceContext createUsbDeviceContextForDevice(int deviceId) {
|
||||
private UsbDeviceContext createUsbDeviceContextForDevice(AbstractController device) {
|
||||
UsbDeviceContext context = new UsbDeviceContext();
|
||||
|
||||
context.id = deviceId;
|
||||
context.id = device.getControllerId();
|
||||
context.device = device;
|
||||
|
||||
context.leftStickDeadzoneRadius = (float) stickDeadzone;
|
||||
context.rightStickDeadzoneRadius = (float) stickDeadzone;
|
||||
@@ -393,6 +417,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.name = devName;
|
||||
context.id = dev.getId();
|
||||
|
||||
if (dev.getVibrator().hasVibrator()) {
|
||||
context.vibrator = dev.getVibrator();
|
||||
}
|
||||
|
||||
context.leftStickXAxis = MotionEvent.AXIS_X;
|
||||
context.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
|
||||
@@ -614,7 +642,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
private short getActiveControllerMask() {
|
||||
if (prefConfig.multiController) {
|
||||
return (short)(currentControllers | initialControllers);
|
||||
return (short)(currentControllers | initialControllers | (prefConfig.onscreenController ? 1 : 0));
|
||||
}
|
||||
else {
|
||||
// Only Player 1 is active with multi-controller disabled
|
||||
@@ -1043,6 +1071,94 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||
// Since we can only use a single amplitude value, compute the desired amplitude
|
||||
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
|
||||
// NB: This value is now 0-255 as required by VibrationEffect.
|
||||
short lowFreqMotorMSB = (short)((lowFreqMotor >> 8) & 0xFF);
|
||||
short highFreqMotorMSB = (short)((highFreqMotor >> 8) & 0xFF);
|
||||
int simulatedAmplitude = Math.min(255, (int)((lowFreqMotorMSB * 0.80) + (highFreqMotorMSB * 0.33)));
|
||||
|
||||
if (simulatedAmplitude == 0) {
|
||||
// This case is easy - just cancel the current effect and get out.
|
||||
// NB: We cannot simply check lowFreqMotor == highFreqMotor == 0
|
||||
// because our simulatedAmplitude could be 0 even though our inputs
|
||||
// are not (ex: lowFreqMotor == 0 && highFreqMotor == 1).
|
||||
vibrator.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to use amplitude-based control if we're on Oreo and the device
|
||||
// supports amplitude-based vibration control.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (vibrator.hasAmplitudeControl()) {
|
||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach this point, we don't have amplitude controls available, so
|
||||
// we must emulate it by PWMing the vibration. Ick.
|
||||
long pwmPeriod = 20;
|
||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||
long offTime = pwmPeriod - onTime;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(new long[]{0, onTime, offTime}, 0, audioAttributes);
|
||||
}
|
||||
else {
|
||||
vibrator.vibrate(new long[]{0, onTime, offTime}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRumble(short controllerNumber, short lowFreqMotor, short highFreqMotor) {
|
||||
boolean foundMatchingDevice = false;
|
||||
boolean vibrated = false;
|
||||
|
||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
|
||||
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
foundMatchingDevice = true;
|
||||
|
||||
if (deviceContext.vibrator != null) {
|
||||
vibrated = true;
|
||||
rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < usbDeviceContexts.size(); i++) {
|
||||
UsbDeviceContext deviceContext = usbDeviceContexts.valueAt(i);
|
||||
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
foundMatchingDevice = vibrated = true;
|
||||
deviceContext.device.rumble((short)lowFreqMotor, (short)highFreqMotor);
|
||||
}
|
||||
}
|
||||
|
||||
// We may decide to rumble the device for player 1
|
||||
if (controllerNumber == 0) {
|
||||
// If we didn't find a matching device, it must be the on-screen
|
||||
// controls that triggered the rumble. Vibrate the device if
|
||||
// the user has requested that behavior.
|
||||
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
|
||||
// We found a device to vibrate but it didn't have rumble support. The user
|
||||
// has requested us to vibrate the device in this case.
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(KeyEvent event) {
|
||||
InputDeviceContext context = getContextForEvent(event);
|
||||
if (context == null) {
|
||||
@@ -1294,12 +1410,30 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return true;
|
||||
}
|
||||
|
||||
public void reportOscState(short buttonFlags,
|
||||
short leftStickX, short leftStickY,
|
||||
short rightStickX, short rightStickY,
|
||||
byte leftTrigger, byte rightTrigger) {
|
||||
defaultContext.leftStickX = leftStickX;
|
||||
defaultContext.leftStickY = leftStickY;
|
||||
|
||||
defaultContext.rightStickX = rightStickX;
|
||||
defaultContext.rightStickY = rightStickY;
|
||||
|
||||
defaultContext.leftTrigger = leftTrigger;
|
||||
defaultContext.rightTrigger = rightTrigger;
|
||||
|
||||
defaultContext.inputMap = buttonFlags;
|
||||
|
||||
sendControllerInputPacket(defaultContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportControllerState(int controllerId, short buttonFlags,
|
||||
float leftStickX, float leftStickY,
|
||||
float rightStickX, float rightStickY,
|
||||
float leftTrigger, float rightTrigger) {
|
||||
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||
GenericControllerContext context = usbDeviceContexts.get(controllerId);
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1334,19 +1468,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceRemoved(int controllerId) {
|
||||
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||
public void deviceRemoved(AbstractController controller) {
|
||||
UsbDeviceContext context = usbDeviceContexts.get(controller.getControllerId());
|
||||
if (context != null) {
|
||||
LimeLog.info("Removed controller: "+controllerId);
|
||||
LimeLog.info("Removed controller: "+controller.getControllerId());
|
||||
releaseControllerNumber(context);
|
||||
usbDeviceContexts.remove(controllerId);
|
||||
usbDeviceContexts.remove(controller.getControllerId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceAdded(int controllerId) {
|
||||
UsbDeviceContext context = createUsbDeviceContextForDevice(controllerId);
|
||||
usbDeviceContexts.put(controllerId, context);
|
||||
public void deviceAdded(AbstractController controller) {
|
||||
UsbDeviceContext context = createUsbDeviceContextForDevice(controller);
|
||||
usbDeviceContexts.put(controller.getControllerId(), context);
|
||||
}
|
||||
|
||||
class GenericControllerContext {
|
||||
@@ -1375,6 +1509,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
class InputDeviceContext extends GenericControllerContext {
|
||||
public String name;
|
||||
public Vibrator vibrator;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
@@ -1412,5 +1547,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public long startDownTime = 0;
|
||||
}
|
||||
|
||||
class UsbDeviceContext extends GenericControllerContext {}
|
||||
class UsbDeviceContext extends GenericControllerContext {
|
||||
public AbstractController device;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,21 @@ public class KeyboardTranslator {
|
||||
public static final int VK_BACK_QUOTE = 192;
|
||||
public static final int VK_QUOTE = 222;
|
||||
public static final int VK_PAUSE = 19;
|
||||
|
||||
|
||||
public static boolean needsShift(int keycode) {
|
||||
switch (keycode)
|
||||
{
|
||||
case KeyEvent.KEYCODE_AT:
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
case KeyEvent.KEYCODE_PLUS:
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
@@ -116,7 +130,8 @@ public class KeyboardTranslator {
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
translated = 0x0d;
|
||||
break;
|
||||
|
||||
|
||||
case KeyEvent.KEYCODE_PLUS:
|
||||
case KeyEvent.KEYCODE_EQUALS:
|
||||
translated = 0xbb;
|
||||
break;
|
||||
@@ -257,7 +272,19 @@ public class KeyboardTranslator {
|
||||
case KeyEvent.KEYCODE_NUMPAD_DOT:
|
||||
translated = 0x6E;
|
||||
break;
|
||||
|
||||
|
||||
case KeyEvent.KEYCODE_AT:
|
||||
translated = 2 + VK_0;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
translated = 3 + VK_0;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
translated = 8 + VK_0;
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("No key for "+keycode);
|
||||
return 0;
|
||||
|
||||
@@ -37,11 +37,13 @@ public abstract class AbstractController {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public abstract void rumble(short lowFreqMotor, short highFreqMotor);
|
||||
|
||||
protected void notifyDeviceRemoved() {
|
||||
listener.deviceRemoved(deviceId);
|
||||
listener.deviceRemoved(this);
|
||||
}
|
||||
|
||||
protected void notifyDeviceAdded() {
|
||||
listener.deviceAdded(deviceId);
|
||||
listener.deviceAdded(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,9 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
|
||||
stopped = true;
|
||||
|
||||
// Cancel any rumble effects
|
||||
rumble((short)0, (short)0);
|
||||
|
||||
// Stop the input thread
|
||||
if (inputThread != null) {
|
||||
inputThread.interrupt();
|
||||
|
||||
@@ -6,6 +6,6 @@ public interface UsbDriverListener {
|
||||
float rightStickX, float rightStickY,
|
||||
float leftTrigger, float rightTrigger);
|
||||
|
||||
void deviceRemoved(int controllerId);
|
||||
void deviceAdded(int controllerId);
|
||||
void deviceRemoved(AbstractController controller);
|
||||
void deviceAdded(AbstractController controller);
|
||||
}
|
||||
|
||||
@@ -47,26 +47,21 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceRemoved(int controllerId) {
|
||||
public void deviceRemoved(AbstractController controller) {
|
||||
// Remove the the controller from our list (if not removed already)
|
||||
for (AbstractController controller : controllers) {
|
||||
if (controller.getControllerId() == controllerId) {
|
||||
controllers.remove(controller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
controllers.remove(controller);
|
||||
|
||||
// Call through to the client's listener
|
||||
if (listener != null) {
|
||||
listener.deviceRemoved(controllerId);
|
||||
listener.deviceRemoved(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceAdded(int controllerId) {
|
||||
public void deviceAdded(AbstractController controller) {
|
||||
// Call through to the client's listener
|
||||
if (listener != null) {
|
||||
listener.deviceAdded(controllerId);
|
||||
listener.deviceAdded(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +108,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
// Report all controllerMap that already exist
|
||||
if (listener != null) {
|
||||
for (AbstractController controller : controllers) {
|
||||
listener.deviceAdded(controller.getControllerId());
|
||||
listener.deviceAdded(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
|
||||
|
||||
private static final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
@@ -23,6 +24,7 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // Unknown
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
@@ -137,4 +139,17 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
// No need to fail init if the LED command fails
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rumble(short lowFreqMotor, short highFreqMotor) {
|
||||
byte[] data = {
|
||||
0x00, 0x08, 0x00,
|
||||
(byte)(lowFreqMotor >> 8), (byte)(highFreqMotor >> 8),
|
||||
0x00, 0x00, 0x00
|
||||
};
|
||||
int res = connection.bulkTransfer(outEndpt, data, data.length, 100);
|
||||
if (res != data.length) {
|
||||
LimeLog.warning("Rumble transfer failed: "+res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class XboxOneController extends AbstractXboxController {
|
||||
|
||||
@@ -23,8 +24,31 @@ public class XboxOneController extends AbstractXboxController {
|
||||
0x24c6, // PowerA
|
||||
};
|
||||
|
||||
// FIXME: odata_serial
|
||||
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||
private static final byte[] HORI_INIT = {0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
|
||||
0x00, 0x00, 0x00, (byte)0x80, 0x00};
|
||||
private static final byte[] PDP_INIT1 = {0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
|
||||
private static final byte[] PDP_INIT2 = {0x06, 0x20, 0x00, 0x02, 0x01, 0x00};
|
||||
private static final byte[] RUMBLE_INIT1 = {0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
|
||||
0x1D, 0x1D, (byte)0xFF, 0x00, 0x00};
|
||||
private static final byte[] RUMBLE_INIT2 = {0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
private static InitPacket[] INIT_PKTS = {
|
||||
new InitPacket(0x0e6f, 0x0165, HORI_INIT),
|
||||
new InitPacket(0x0f0d, 0x0067, HORI_INIT),
|
||||
new InitPacket(0x0000, 0x0000, FW2015_INIT),
|
||||
new InitPacket(0x0e6f, 0x0000, PDP_INIT1),
|
||||
new InitPacket(0x0e6f, 0x0000, PDP_INIT2),
|
||||
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT1),
|
||||
new InitPacket(0x24c6, 0x542a, RUMBLE_INIT1),
|
||||
new InitPacket(0x24c6, 0x543a, RUMBLE_INIT1),
|
||||
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT2),
|
||||
new InitPacket(0x24c6, 0x542a, RUMBLE_INIT2),
|
||||
new InitPacket(0x24c6, 0x543a, RUMBLE_INIT2),
|
||||
};
|
||||
|
||||
private byte seqNum = 0;
|
||||
|
||||
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||
super(device, connection, deviceId, listener);
|
||||
@@ -111,13 +135,55 @@ public class XboxOneController extends AbstractXboxController {
|
||||
|
||||
@Override
|
||||
protected boolean doInit() {
|
||||
// Send the initialization packet
|
||||
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
|
||||
if (res != XB1_INIT_DATA.length) {
|
||||
LimeLog.warning("Initialization transfer failed: "+res);
|
||||
return false;
|
||||
// Send all applicable init packets
|
||||
for (InitPacket pkt : INIT_PKTS) {
|
||||
if (pkt.vendorId != 0 && device.getVendorId() != pkt.vendorId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pkt.productId != 0 && device.getProductId() != pkt.productId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] data = Arrays.copyOf(pkt.data, pkt.data.length);
|
||||
|
||||
// Populate sequence number
|
||||
data[2] = seqNum++;
|
||||
|
||||
// Send the initialization packet
|
||||
int res = connection.bulkTransfer(outEndpt, data, data.length, 3000);
|
||||
if (res != data.length) {
|
||||
LimeLog.warning("Initialization transfer failed: "+res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rumble(short lowFreqMotor, short highFreqMotor) {
|
||||
byte[] data = {
|
||||
0x09, 0x00, seqNum++, 0x09, 0x00,
|
||||
0x0F, 0x00, 0x00,
|
||||
(byte)(lowFreqMotor >> 9), (byte)(highFreqMotor >> 9),
|
||||
(byte)0xFF, 0x00, (byte)0xFF
|
||||
};
|
||||
int res = connection.bulkTransfer(outEndpt, data, data.length, 100);
|
||||
if (res != data.length) {
|
||||
LimeLog.warning("Rumble transfer failed: "+res);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InitPacket {
|
||||
final int vendorId;
|
||||
final int productId;
|
||||
final byte[] data;
|
||||
|
||||
InitPacket(int vendorId, int productId, byte[] data) {
|
||||
this.vendorId = vendorId;
|
||||
this.productId = productId;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-22
@@ -13,6 +13,7 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.ControllerHandler;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -38,7 +39,7 @@ public class VirtualController {
|
||||
|
||||
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
||||
|
||||
private NvConnection connection = null;
|
||||
private ControllerHandler controllerHandler;
|
||||
private Context context = null;
|
||||
|
||||
private FrameLayout frame_layout = null;
|
||||
@@ -53,8 +54,8 @@ public class VirtualController {
|
||||
|
||||
private List<VirtualControllerElement> elements = new ArrayList<>();
|
||||
|
||||
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) {
|
||||
this.connection = conn;
|
||||
public VirtualController(final ControllerHandler controllerHandler, FrameLayout layout, final Context context) {
|
||||
this.controllerHandler = controllerHandler;
|
||||
this.frame_layout = layout;
|
||||
this.context = context;
|
||||
|
||||
@@ -167,26 +168,22 @@ public class VirtualController {
|
||||
}
|
||||
|
||||
void sendControllerInputContext() {
|
||||
try {
|
||||
_DBG("INPUT_MAP + " + inputContext.inputMap);
|
||||
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
|
||||
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
|
||||
_DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY);
|
||||
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
|
||||
_DBG("INPUT_MAP + " + inputContext.inputMap);
|
||||
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
|
||||
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
|
||||
_DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY);
|
||||
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
|
||||
|
||||
if (connection != null) {
|
||||
connection.sendControllerInput(
|
||||
inputContext.inputMap,
|
||||
inputContext.leftTrigger,
|
||||
inputContext.rightTrigger,
|
||||
inputContext.leftStickX,
|
||||
inputContext.leftStickY,
|
||||
inputContext.rightStickX,
|
||||
inputContext.rightStickY
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (controllerHandler != null) {
|
||||
controllerHandler.reportOscState(
|
||||
inputContext.inputMap,
|
||||
inputContext.leftStickX,
|
||||
inputContext.leftStickY,
|
||||
inputContext.rightStickX,
|
||||
inputContext.rightStickY,
|
||||
inputContext.leftTrigger,
|
||||
inputContext.rightTrigger
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
-46
@@ -166,58 +166,54 @@ public abstract class VirtualControllerElement extends View {
|
||||
}
|
||||
|
||||
protected void showConfigurationDialog() {
|
||||
try {
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
|
||||
|
||||
alertBuilder.setTitle("Configuration");
|
||||
alertBuilder.setTitle("Configuration");
|
||||
|
||||
CharSequence functions[] = new CharSequence[]{
|
||||
"Move",
|
||||
"Resize",
|
||||
/*election
|
||||
"Set n
|
||||
Disable color sormal color",
|
||||
"Set pressed color",
|
||||
CharSequence functions[] = new CharSequence[]{
|
||||
"Move",
|
||||
"Resize",
|
||||
/*election
|
||||
"Set n
|
||||
Disable color sormal color",
|
||||
"Set pressed color",
|
||||
*/
|
||||
"Cancel"
|
||||
};
|
||||
|
||||
alertBuilder.setItems(functions, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case 0: { // move
|
||||
actionEnableMove();
|
||||
break;
|
||||
}
|
||||
case 1: { // resize
|
||||
actionEnableResize();
|
||||
break;
|
||||
}
|
||||
/*
|
||||
case 2: { // set default color
|
||||
actionShowNormalColorChooser();
|
||||
break;
|
||||
}
|
||||
case 3: { // set pressed color
|
||||
actionShowPressedColorChooser();
|
||||
break;
|
||||
}
|
||||
*/
|
||||
"Cancel"
|
||||
};
|
||||
|
||||
alertBuilder.setItems(functions, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case 0: { // move
|
||||
actionEnableMove();
|
||||
break;
|
||||
}
|
||||
case 1: { // resize
|
||||
actionEnableResize();
|
||||
break;
|
||||
}
|
||||
/*
|
||||
case 2: { // set default color
|
||||
actionShowNormalColorChooser();
|
||||
default: { // cancel
|
||||
actionCancel();
|
||||
break;
|
||||
}
|
||||
case 3: { // set pressed color
|
||||
actionShowPressedColorChooser();
|
||||
break;
|
||||
}
|
||||
*/
|
||||
default: { // cancel
|
||||
actionCancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
AlertDialog alert = alertBuilder.create();
|
||||
// show menu
|
||||
alert.show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
AlertDialog alert = alertBuilder.create();
|
||||
// show menu
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.limelight.computers;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
@@ -9,9 +8,7 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import android.content.ContentValues;
|
||||
@@ -79,7 +76,7 @@ public class ComputerDatabaseManager {
|
||||
|
||||
public boolean updateComputer(ComputerDetails details) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid);
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
|
||||
values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
|
||||
@@ -102,14 +99,7 @@ public class ComputerDatabaseManager {
|
||||
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
String uuidStr = c.getString(0);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||
}
|
||||
|
||||
details.uuid = c.getString(0);
|
||||
details.name = c.getString(1);
|
||||
details.localAddress = c.getString(2);
|
||||
details.remoteAddress = c.getString(3);
|
||||
@@ -152,8 +142,8 @@ public class ComputerDatabaseManager {
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerByUUID(UUID uuid) {
|
||||
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null);
|
||||
public ComputerDetails getComputerByUUID(String uuid) {
|
||||
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid }, null, null, null);
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
c.close();
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.net.Socket;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
@@ -18,6 +17,7 @@ import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
@@ -241,7 +241,7 @@ public class ComputerManagerService extends Service {
|
||||
return idManager.getUniqueId();
|
||||
}
|
||||
|
||||
public ComputerDetails getComputer(UUID uuid) {
|
||||
public ComputerDetails getComputer(String uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (uuid.equals(tuple.computer.uuid)) {
|
||||
@@ -253,7 +253,7 @@ public class ComputerManagerService extends Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void invalidateStateForComputer(UUID uuid) {
|
||||
public void invalidateStateForComputer(String uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (uuid.equals(tuple.computer.uuid)) {
|
||||
@@ -360,13 +360,14 @@ public class ComputerManagerService extends Service {
|
||||
// Since we're on the same network, we can use STUN to find
|
||||
// our WAN address, which is also very likely the WAN address
|
||||
// of the PC. We can use this later to connect remotely.
|
||||
fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns();
|
||||
fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
|
||||
}
|
||||
|
||||
// Block while we try to fill the details
|
||||
try {
|
||||
runPoll(fakeDetails, true, 0);
|
||||
if (fakeDetails.state == ComputerDetails.State.ONLINE) {
|
||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||
if (pollComputer(fakeDetails)) {
|
||||
// See if we have record of this PC to pull its pinned cert
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
@@ -377,10 +378,9 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
if (fakeDetails.serverCert != null) {
|
||||
// Poll again with the pinned cert to get accurate pairing information
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||
// This will insert the host into the database too.
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
@@ -457,8 +457,12 @@ public class ComputerManagerService extends Service {
|
||||
ComputerDetails newDetails = http.getComputerDetails();
|
||||
|
||||
// Check if this is the PC we expected
|
||||
if (details.uuid != null && newDetails.uuid != null &&
|
||||
!details.uuid.equals(newDetails.uuid)) {
|
||||
if (newDetails.uuid == null) {
|
||||
LimeLog.severe("Polling returned no UUID!");
|
||||
return null;
|
||||
}
|
||||
// details.uuid can be null on initial PC add
|
||||
else if (details.uuid != null && !details.uuid.equals(newDetails.uuid)) {
|
||||
// We got the wrong PC!
|
||||
LimeLog.info("Polling returned the wrong PC!");
|
||||
return null;
|
||||
@@ -468,7 +472,8 @@ public class ComputerManagerService extends Service {
|
||||
newDetails.activeAddress = address;
|
||||
|
||||
return newDetails;
|
||||
} catch (Exception e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -697,8 +702,9 @@ public class ComputerManagerService extends Service {
|
||||
public void run() {
|
||||
int emptyAppListResponses = 0;
|
||||
do {
|
||||
// Can't poll if it's not online
|
||||
if (computer.state != ComputerDetails.State.ONLINE) {
|
||||
// Can't poll if it's not online or paired
|
||||
if (computer.state != ComputerDetails.State.ONLINE ||
|
||||
computer.pairState != PairingManager.PairState.PAIRED) {
|
||||
if (listener != null) {
|
||||
listener.notifyComputerUpdated(computer);
|
||||
}
|
||||
@@ -738,12 +744,12 @@ public class ComputerManagerService extends Service {
|
||||
// in a row, we'll go ahead and believe it.
|
||||
emptyAppListResponses++;
|
||||
}
|
||||
if (appList != null && !appList.isEmpty() &&
|
||||
if (!appList.isEmpty() &&
|
||||
(!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) {
|
||||
// Open the cache file
|
||||
OutputStream cacheOut = null;
|
||||
try {
|
||||
cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid.toString());
|
||||
cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid);
|
||||
CacheHelper.writeStringToOutputStream(cacheOut, appList);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -770,7 +776,7 @@ public class ComputerManagerService extends Service {
|
||||
listener.notifyComputerUpdated(computer);
|
||||
}
|
||||
}
|
||||
else if (appList == null || appList.isEmpty()) {
|
||||
else if (appList.isEmpty()) {
|
||||
LimeLog.warning("Null app list received from "+computer.uuid);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LegacyDatabaseReader {
|
||||
private static final String COMPUTER_DB_NAME = "computers.db";
|
||||
@@ -24,14 +23,7 @@ public class LegacyDatabaseReader {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
details.name = c.getString(0);
|
||||
|
||||
String uuidStr = c.getString(1);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for " + details.name);
|
||||
}
|
||||
details.uuid = c.getString(1);
|
||||
|
||||
// An earlier schema defined addresses as byte blobs. We'll
|
||||
// gracefully migrate those to strings so we can store DNS names
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.widget.TextView;
|
||||
import com.limelight.PcView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -77,6 +78,14 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
overlayView.setAlpha(0.4f);
|
||||
return true;
|
||||
}
|
||||
// We must check if the status is exactly online and unpaired
|
||||
// to avoid colliding with the loading spinner when status is unknown
|
||||
else if (obj.details.state == ComputerDetails.State.ONLINE &&
|
||||
obj.details.pairState == PairingManager.PairState.NOT_PAIRED) {
|
||||
overlayView.setImageResource(R.drawable.ic_lock);
|
||||
overlayView.setAlpha(1.0f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class DiskAssetLoader {
|
||||
}
|
||||
|
||||
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
}
|
||||
|
||||
// https://developer.android.com/topic/performance/graphics/load-bitmap.html
|
||||
@@ -65,7 +65,7 @@ public class DiskAssetLoader {
|
||||
}
|
||||
|
||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
||||
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
|
||||
// Don't bother with anything if it doesn't exist
|
||||
if (!file.exists()) {
|
||||
@@ -137,7 +137,7 @@ public class DiskAssetLoader {
|
||||
OutputStream out = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
|
||||
success = true;
|
||||
} catch (IOException e) {
|
||||
@@ -151,7 +151,7 @@ public class DiskAssetLoader {
|
||||
|
||||
if (!success) {
|
||||
LimeLog.warning("Unable to populate cache with tuple: "+tuple);
|
||||
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class MemoryAssetLoader {
|
||||
};
|
||||
|
||||
private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
return tuple.computer.uuid.toString()+"-"+tuple.app.getAppId();
|
||||
return tuple.computer.uuid+"-"+tuple.app.getAppId();
|
||||
}
|
||||
|
||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
|
||||
@@ -91,14 +91,20 @@ public class AddComputerManually extends Activity {
|
||||
}
|
||||
|
||||
private void doAddPc(String host) {
|
||||
String msg;
|
||||
boolean wrongSiteLocal = false;
|
||||
boolean success;
|
||||
|
||||
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
|
||||
getResources().getString(R.string.msg_add_pc), false);
|
||||
|
||||
success = managerBinder.addComputerBlocking(host, true);
|
||||
try {
|
||||
success = managerBinder.addComputerBlocking(host, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
|
||||
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
}
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.limelight.preferences;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
@@ -35,6 +35,8 @@ public class PreferenceConfiguration {
|
||||
private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation";
|
||||
private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons";
|
||||
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
|
||||
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
|
||||
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
|
||||
|
||||
static final String DEFAULT_RESOLUTION = "720p";
|
||||
static final String DEFAULT_FPS = "60";
|
||||
@@ -58,6 +60,8 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_MOUSE_EMULATION = true;
|
||||
private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false;
|
||||
private static final boolean DEFAULT_UNLOCK_FPS = false;
|
||||
private static final boolean DEFAULT_VIBRATE_OSC = true;
|
||||
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
@@ -79,11 +83,16 @@ public class PreferenceConfiguration {
|
||||
public boolean mouseEmulation;
|
||||
public boolean mouseNavButtons;
|
||||
public boolean unlockFps;
|
||||
public boolean vibrateOsc;
|
||||
public boolean vibrateFallbackToDevice;
|
||||
|
||||
private static int getHeightFromResolutionString(String resString) {
|
||||
if (resString.equalsIgnoreCase("360p")) {
|
||||
return 360;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("480p")) {
|
||||
return 480;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("720p")) {
|
||||
return 720;
|
||||
}
|
||||
@@ -103,13 +112,22 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
|
||||
private static int getWidthFromResolutionString(String resString) {
|
||||
return (getHeightFromResolutionString(resString) * 16) / 9;
|
||||
int height = getHeightFromResolutionString(resString);
|
||||
if (height == 480) {
|
||||
// This isn't an exact 16:9 resolution
|
||||
return 854;
|
||||
}
|
||||
else {
|
||||
return (height * 16) / 9;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getResolutionString(int width, int height) {
|
||||
switch (height) {
|
||||
case 360:
|
||||
return "360p";
|
||||
case 480:
|
||||
return "480p";
|
||||
default:
|
||||
case 720:
|
||||
return "720p";
|
||||
@@ -139,6 +157,9 @@ public class PreferenceConfiguration {
|
||||
if (width * height <= 640 * 360) {
|
||||
return (int)(1000 * (fps / 30.0));
|
||||
}
|
||||
else if (width * height <= 854 * 480) {
|
||||
return (int)(1500 * (fps / 30.0));
|
||||
}
|
||||
// This covers 1280x720 and 1280x800 too
|
||||
else if (width * height <= 1366 * 768) {
|
||||
return (int)(5000 * (fps / 30.0));
|
||||
@@ -314,6 +335,8 @@ public class PreferenceConfiguration {
|
||||
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
|
||||
config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS);
|
||||
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
|
||||
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
|
||||
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
|
||||
@@ -30,7 +33,7 @@ public class ServerHelper {
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
|
||||
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid);
|
||||
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
|
||||
try {
|
||||
if (computer.serverCert != null) {
|
||||
@@ -84,8 +87,9 @@ public class ServerHelper {
|
||||
message = parent.getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = parent.getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
message = e.getMessage();
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (onComplete != null) {
|
||||
onComplete.run();
|
||||
|
||||
@@ -125,7 +125,7 @@ public class ShortcutHelper {
|
||||
}
|
||||
|
||||
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
|
||||
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
|
||||
createAppViewShortcut(id, details.name, details.uuid, forceAdd);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
@@ -158,7 +158,7 @@ public class ShortcutHelper {
|
||||
}
|
||||
|
||||
public boolean createPinnedGameShortcut(String id, Bitmap iconBits, ComputerDetails cDetails, NvApp app) {
|
||||
return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid.toString(), app.getAppName(), Integer.valueOf(app.getAppId()).toString());
|
||||
return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid, app.getAppName(), Integer.valueOf(app.getAppId()).toString());
|
||||
}
|
||||
|
||||
public void disableShortcut(String id, CharSequence reason) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<vector android:height="128dp" android:width="128dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
|
||||
</vector>
|
||||
|
||||
@@ -10,4 +10,16 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notificationOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_gravity="right"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="right"
|
||||
android:background="#80000000"
|
||||
android:visibility="gone" />
|
||||
|
||||
</merge>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Shortcut strings -->
|
||||
<string name="scut_deleted_pc">PC supprimé</string>
|
||||
<string name="scut_not_paired">PC non appairé</string>
|
||||
@@ -133,6 +134,8 @@
|
||||
<string name="category_input_settings">Paramètres d\'entrée</string>
|
||||
<string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string>
|
||||
<string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Emuler le support vibration</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Vibre votre appareil pour émuler une vibration si votre manette ne le prend pas en charge</string>
|
||||
<string name="title_seekbar_deadzone">Régler la zone morte du stick analogique</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Pilote de contrôleur Xbox 360/One</string>
|
||||
@@ -147,6 +150,8 @@
|
||||
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
|
||||
<string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string>
|
||||
<string name="title_checkbox_vibrate_osc">Activer les vibrations</string>
|
||||
<string name="summary_checkbox_vibrate_osc">Vibre votre appareil pour émuler les vibrations des commandes à l\'écran</string>
|
||||
<string name="title_only_l3r3">Montre seulement L3 et R3</string>
|
||||
<string name="summary_only_l3r3">Cacher tout sauf L3 et R3</string>
|
||||
<string name="title_reset_osc">Effacer la disposition des commandes à l\'écran sauvegardée</string>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<resources>
|
||||
<string-array name="resolution_names">
|
||||
<item>360p</item>
|
||||
<item>480p</item>
|
||||
<item>720p</item>
|
||||
<item>1080p</item>
|
||||
<item>1440p</item>
|
||||
@@ -9,6 +10,7 @@
|
||||
</string-array>
|
||||
<string-array name="resolution_values" translatable="false">
|
||||
<item>360p</item>
|
||||
<item>480p</item>
|
||||
<item>720p</item>
|
||||
<item>1080p</item>
|
||||
<item>1440p</item>
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
<string name="title_details">Details</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="delete_pc_msg">Are you sure you want to delete this PC?</string>
|
||||
<string name="slow_connection_msg">Slow connection to PC\nReduce your bitrate</string>
|
||||
<string name="poor_connection_msg">Poor connection to PC</string>
|
||||
|
||||
<!-- AppList activity -->
|
||||
<string name="applist_connect_msg">Connecting to PC…</string>
|
||||
@@ -136,6 +138,8 @@
|
||||
<string name="category_input_settings">Input Settings</string>
|
||||
<string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string>
|
||||
<string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Emulate rumble support with vibration</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Vibrates your device to emulate rumble if your gamepad does not support it</string>
|
||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string>
|
||||
@@ -150,6 +154,8 @@
|
||||
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
||||
<string name="summary_checkbox_show_onscreen_controls">Show virtual controller overlay on touchscreen</string>
|
||||
<string name="title_checkbox_vibrate_osc">Enable vibration</string>
|
||||
<string name="summary_checkbox_vibrate_osc">Vibrates your device to emulate rumble for the on-screen controls</string>
|
||||
<string name="title_only_l3r3">Only show L3 and R3</string>
|
||||
<string name="summary_only_l3r3">Hide all virtual buttons except L3 and R3</string>
|
||||
<string name="title_reset_osc">Clear saved on-screen controls layout</string>
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
android:title="@string/title_checkbox_mouse_emulation"
|
||||
android:summary="@string/summary_checkbox_mouse_emulation"
|
||||
android:defaultValue="true" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_vibrate_fallback"
|
||||
android:title="@string/title_checkbox_vibrate_fallback"
|
||||
android:summary="@string/summary_checkbox_vibrate_fallback"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
|
||||
android:key="category_onscreen_controls">
|
||||
@@ -90,6 +95,12 @@
|
||||
android:key="checkbox_show_onscreen_controls"
|
||||
android:summary="@string/summary_checkbox_show_onscreen_controls"
|
||||
android:title="@string/title_checkbox_show_onscreen_controls" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_vibrate_osc"
|
||||
android:dependency="checkbox_show_onscreen_controls"
|
||||
android:title="@string/title_checkbox_vibrate_osc"
|
||||
android:summary="@string/summary_checkbox_vibrate_osc"
|
||||
android:defaultValue="true" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:dependency="checkbox_show_onscreen_controls"
|
||||
@@ -138,6 +149,11 @@
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_advanced_settings"
|
||||
android:key="category_advanced_settings">
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_disable_warnings"
|
||||
android:title="@string/title_checkbox_disable_warnings"
|
||||
android:summary="@string/summary_checkbox_disable_warnings"
|
||||
android:defaultValue="false" />
|
||||
<ListPreference
|
||||
android:key="video_format"
|
||||
android:title="@string/title_video_format"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Tue May 08 18:56:31 PDT 2018
|
||||
#Fri Apr 26 18:29:34 PDT 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
||||
+1
-1
Submodule moonlight-common updated: ee6fd70642...9f409da618
Reference in New Issue
Block a user