Compare commits

...

19 Commits

Author SHA1 Message Date
Cameron Gutman d1e135db4d Version 6.2 2019-02-06 22:10:29 -08:00
Cameron Gutman 61a17afe69 Fix *, @, #, and + keys on software keyboard 2019-02-06 21:40:28 -08:00
Cameron Gutman 47fd691884 Update to AGP 3.3.1 2019-02-06 21:14:50 -08:00
Cameron Gutman 0d171c6b28 Fix lock icon drawing on top of the loading spinner 2019-02-06 21:14:01 -08:00
Cameron Gutman f0c69d08b8 Add 480p option 2019-02-06 21:09:04 -08:00
Cameron Gutman 629bf5766d Fix a couple crash reports 2019-02-05 22:51:48 -08:00
Cameron Gutman 233bceeece Update common for GFE 3.17 2019-02-05 22:10:11 -08:00
Cameron Gutman 6660ea7d91 Update Xbox driver with Linux xpad.c and init quirks 2019-02-05 21:52:53 -08:00
Cameron Gutman 4864b2ca45 Add lock icon when PC is unpaired 2019-02-05 21:10:09 -08:00
Cameron Gutman 92097b318d Update Gradle and AGP 2019-02-05 20:58:49 -08:00
Cameron Gutman 997898c99d Version 6.1.3 2019-01-04 18:20:28 -08:00
Cameron Gutman 1174e03885 Fix incorrectly persisting host with missing server cert 2019-01-04 18:18:32 -08:00
Cameron Gutman ff0f54d541 Switch to using stun.moonlight-stream.org for STUN 2019-01-04 18:05:28 -08:00
Cameron Gutman 814964a100 Fix exception adding PCs 2019-01-01 23:32:16 -08:00
Cameron Gutman 7e154292a9 Stop suppressing exceptions 2019-01-01 23:31:38 -08:00
Cameron Gutman 0f9cba1053 Fix crash due to a null computer uuid 2019-01-01 22:34:27 -08:00
Cameron Gutman a4e134589d Version 6.1.1 2018-12-27 23:58:30 -08:00
Cameron Gutman cd80a94f28 Fix IllegalStateException caused by making HTTPS request without a pinned cert 2018-12-27 23:55:59 -08:00
Cameron Gutman 57c645a291 Change uuid field to String type due to new format UUIDs that fail to parse on GFE 3.16 2018-12-27 23:48:12 -08:00
27 changed files with 280 additions and 153 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionName "6.1" versionName "6.2"
versionCode = 182 versionCode = 186
} }
flavorDimensions "root" flavorDimensions "root"
+10 -7
View File
@@ -1,8 +1,8 @@
package com.limelight; package com.limelight;
import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.List; import java.util.List;
import java.util.UUID;
import com.limelight.computers.ComputerManagerListener; import com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService; import com.limelight.computers.ComputerManagerService;
@@ -28,7 +28,6 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -45,6 +44,8 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import org.xmlpull.v1.XmlPullParserException;
public class AppView extends Activity implements AdapterFragmentCallbacks { public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter; private AppGridAdapter appGridAdapter;
private String uuidString; private String uuidString;
@@ -82,7 +83,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
localBinder.waitForReady(); localBinder.waitForReady();
// Get the computer object // Get the computer object
computer = localBinder.getComputer(UUID.fromString(uuidString)); computer = localBinder.getComputer(uuidString);
if (computer == null) { if (computer == null) {
finish(); finish();
return; return;
@@ -156,7 +157,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
} }
// Don't care about other computers // Don't care about other computers
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) { if (!details.uuid.equalsIgnoreCase(uuidString)) {
return; return;
} }
@@ -180,7 +181,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
@Override @Override
public void run() { public void run() {
// Disable shortcuts referencing this PC for now // Disable shortcuts referencing this PC for now
shortcutHelper.disableShortcut(details.uuid.toString(), shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_not_paired)); getResources().getString(R.string.scut_not_paired));
// Display a toast to the user and quit the activity // Display a toast to the user and quit the activity
@@ -216,7 +217,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
blockingLoadSpinner.dismiss(); blockingLoadSpinner.dismiss();
blockingLoadSpinner = null; 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)); List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
updateUiWithAppList(applist); updateUiWithAppList(applist);
LimeLog.info("Loaded applist from cache"); LimeLog.info("Loaded applist from cache");
} catch (Exception e) { } catch (IOException | XmlPullParserException e) {
if (lastRawApplist != null) { if (lastRawApplist != null) {
LimeLog.warning("Saved applist corrupted: "+lastRawApplist); LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
e.printStackTrace(); e.printStackTrace();
+14 -2
View File
@@ -908,7 +908,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false; 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; return true;
@@ -959,7 +964,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false; 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; return true;
+8 -5
View File
@@ -53,6 +53,8 @@ import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import org.xmlpull.v1.XmlPullParserException;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; 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); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
e.printStackTrace(); e.printStackTrace();
message = e.getMessage(); message = e.getMessage();
} }
@@ -521,8 +523,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
message = e.getMessage(); message = e.getMessage();
e.printStackTrace();
} }
final String toastMessage = message; final String toastMessage = message;
@@ -548,7 +551,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Intent i = new Intent(this, AppView.class); Intent i = new Intent(this, AppView.class);
i.putExtra(AppView.NAME_EXTRA, computer.name); i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); i.putExtra(AppView.UUID_EXTRA, computer.uuid);
startActivity(i); startActivity(i);
} }
@@ -631,7 +634,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (details.equals(computer.details)) { if (details.equals(computer.details)) {
// Disable or delete shortcuts referencing this PC // Disable or delete shortcuts referencing this PC
shortcutHelper.disableShortcut(details.uuid.toString(), shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_deleted_pc)); getResources().getString(R.string.scut_deleted_pc));
pcGridAdapter.removeComputer(computer); pcGridAdapter.removeComputer(computer);
@@ -662,7 +665,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Add a launcher shortcut for this PC // Add a launcher shortcut for this PC
if (details.pairState == PairState.PAIRED) { if (details.pairState == PairState.PAIRED) {
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false); shortcutHelper.createAppViewShortcut(details.uuid, details, false);
} }
if (existingEntry != null) { if (existingEntry != null) {
@@ -48,7 +48,7 @@ public class ShortcutTrampoline extends Activity {
managerBinder = localBinder; managerBinder = localBinder;
// Get the computer object // Get the computer object
computer = managerBinder.getComputer(UUID.fromString(uuidString)); computer = managerBinder.getComputer(uuidString);
if (computer == null) { if (computer == null) {
Dialog.displayDialog(ShortcutTrampoline.this, Dialog.displayDialog(ShortcutTrampoline.this,
@@ -77,7 +77,7 @@ public class ShortcutTrampoline extends Activity {
@Override @Override
public void notifyComputerUpdated(final ComputerDetails details) { public void notifyComputerUpdated(final ComputerDetails details) {
// Don't care about other computers // Don't care about other computers
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) { if (!details.uuid.equalsIgnoreCase(uuidString)) {
return; return;
} }
@@ -200,6 +200,14 @@ public class ShortcutTrampoline extends Activity {
protected boolean validateInput() { protected boolean validateInput() {
// Validate UUID // 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 { try {
UUID.fromString(uuidString); UUID.fromString(uuidString);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
@@ -12,7 +12,6 @@ import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@@ -155,7 +154,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
} catch (Exception e) { } catch (Exception e) {
// Nothing should go wrong here // Nothing should go wrong here
e.printStackTrace(); e.printStackTrace();
return false; throw new RuntimeException(e);
} }
LimeLog.info("Generated a new key pair"); LimeLog.info("Generated a new key pair");
@@ -48,6 +48,20 @@ public class KeyboardTranslator {
public static final int VK_QUOTE = 222; public static final int VK_QUOTE = 222;
public static final int VK_PAUSE = 19; 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 * Translates the given keycode and returns the GFE keycode
* @param keycode the code to be translated * @param keycode the code to be translated
@@ -117,6 +131,7 @@ public class KeyboardTranslator {
translated = 0x0d; translated = 0x0d;
break; break;
case KeyEvent.KEYCODE_PLUS:
case KeyEvent.KEYCODE_EQUALS: case KeyEvent.KEYCODE_EQUALS:
translated = 0xbb; translated = 0xbb;
break; break;
@@ -258,6 +273,18 @@ public class KeyboardTranslator {
translated = 0x6E; translated = 0x6E;
break; 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: default:
System.out.println("No key for "+keycode); System.out.println("No key for "+keycode);
return 0; return 0;
@@ -14,6 +14,7 @@ public class Xbox360Controller extends AbstractXboxController {
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
private static final int[] SUPPORTED_VENDORS = { private static final int[] SUPPORTED_VENDORS = {
0x0079, // GPD Win 2
0x044f, // Thrustmaster 0x044f, // Thrustmaster
0x045e, // Microsoft 0x045e, // Microsoft
0x046d, // Logitech 0x046d, // Logitech
@@ -23,6 +24,7 @@ public class Xbox360Controller extends AbstractXboxController {
0x07ff, // Mad Catz 0x07ff, // Mad Catz
0x0e6f, // Unknown 0x0e6f, // Unknown
0x0f0d, // Hori 0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon 0x11c9, // Nacon
0x12ab, // Unknown 0x12ab, // Unknown
0x1430, // RedOctane 0x1430, // RedOctane
@@ -8,6 +8,7 @@ import com.limelight.LimeLog;
import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
public class XboxOneController extends AbstractXboxController { public class XboxOneController extends AbstractXboxController {
@@ -23,8 +24,30 @@ public class XboxOneController extends AbstractXboxController {
0x24c6, // PowerA 0x24c6, // PowerA
}; };
// FIXME: odata_serial private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
private static final byte[] XB1_INIT_DATA = {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),
};
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) { public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
super(device, connection, deviceId, listener); super(device, connection, deviceId, listener);
@@ -111,13 +134,43 @@ public class XboxOneController extends AbstractXboxController {
@Override @Override
protected boolean doInit() { protected boolean doInit() {
// Send the initialization packet byte seqNum = 0;
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
if (res != XB1_INIT_DATA.length) { // Send all applicable init packets
LimeLog.warning("Initialization transfer failed: "+res); for (InitPacket pkt : INIT_PKTS) {
return false; 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; return true;
} }
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;
}
}
} }
@@ -167,26 +167,22 @@ public class VirtualController {
} }
void sendControllerInputContext() { void sendControllerInputContext() {
try { _DBG("INPUT_MAP + " + inputContext.inputMap);
_DBG("INPUT_MAP + " + inputContext.inputMap); _DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger); _DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger); _DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY);
_DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY); _DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
if (connection != null) { if (connection != null) {
connection.sendControllerInput( connection.sendControllerInput(
inputContext.inputMap, inputContext.inputMap,
inputContext.leftTrigger, inputContext.leftTrigger,
inputContext.rightTrigger, inputContext.rightTrigger,
inputContext.leftStickX, inputContext.leftStickX,
inputContext.leftStickY, inputContext.leftStickY,
inputContext.rightStickX, inputContext.rightStickX,
inputContext.rightStickY inputContext.rightStickY
); );
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
} }
@@ -166,58 +166,54 @@ public abstract class VirtualControllerElement extends View {
} }
protected void showConfigurationDialog() { 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[]{ CharSequence functions[] = new CharSequence[]{
"Move", "Move",
"Resize", "Resize",
/*election /*election
"Set n "Set n
Disable color sormal color", Disable color sormal color",
"Set pressed 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" default: { // cancel
}; actionCancel();
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; break;
} }
case 3: { // set pressed color
actionShowPressedColorChooser();
break;
}
*/
default: { // cancel
actionCancel();
break;
}
}
} }
}); }
AlertDialog alert = alertBuilder.create(); });
// show menu AlertDialog alert = alertBuilder.create();
alert.show(); // show menu
} catch (Exception e) { alert.show();
e.printStackTrace();
}
} }
@Override @Override
@@ -1,7 +1,6 @@
package com.limelight.computers; package com.limelight.computers;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
@@ -9,9 +8,7 @@ import java.security.cert.X509Certificate;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID;
import com.limelight.LimeLog;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import android.content.ContentValues; import android.content.ContentValues;
@@ -79,7 +76,7 @@ public class ComputerDatabaseManager {
public boolean updateComputer(ComputerDetails details) { public boolean updateComputer(ComputerDetails details) {
ContentValues values = new ContentValues(); 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(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress); values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress); values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
@@ -102,14 +99,7 @@ public class ComputerDatabaseManager {
private ComputerDetails getComputerFromCursor(Cursor c) { private ComputerDetails getComputerFromCursor(Cursor c) {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
String uuidStr = c.getString(0); details.uuid = 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.name = c.getString(1); details.name = c.getString(1);
details.localAddress = c.getString(2); details.localAddress = c.getString(2);
details.remoteAddress = c.getString(3); details.remoteAddress = c.getString(3);
@@ -152,8 +142,8 @@ public class ComputerDatabaseManager {
return computerList; return computerList;
} }
public ComputerDetails getComputerByUUID(UUID uuid) { public ComputerDetails getComputerByUUID(String uuid) {
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null); Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid }, null, null, null);
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
// No matching computer // No matching computer
c.close(); c.close();
@@ -8,7 +8,6 @@ import java.net.Socket;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.limelight.LimeLog; import com.limelight.LimeLog;
@@ -18,6 +17,7 @@ import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.http.PairingManager;
import com.limelight.nvstream.mdns.MdnsComputer; import com.limelight.nvstream.mdns.MdnsComputer;
import com.limelight.nvstream.mdns.MdnsDiscoveryListener; import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
import com.limelight.utils.CacheHelper; import com.limelight.utils.CacheHelper;
@@ -241,7 +241,7 @@ public class ComputerManagerService extends Service {
return idManager.getUniqueId(); return idManager.getUniqueId();
} }
public ComputerDetails getComputer(UUID uuid) { public ComputerDetails getComputer(String uuid) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) { if (uuid.equals(tuple.computer.uuid)) {
@@ -253,7 +253,7 @@ public class ComputerManagerService extends Service {
return null; return null;
} }
public void invalidateStateForComputer(UUID uuid) { public void invalidateStateForComputer(String uuid) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) { 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 // Since we're on the same network, we can use STUN to find
// our WAN address, which is also very likely the WAN address // our WAN address, which is also very likely the WAN address
// of the PC. We can use this later to connect remotely. // 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 // Block while we try to fill the details
try { try {
runPoll(fakeDetails, true, 0); // We cannot use runPoll() here because it will attempt to persist the state of the machine
if (fakeDetails.state == ComputerDetails.State.ONLINE) { // 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 // See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
@@ -377,10 +378,9 @@ public class ComputerManagerService extends Service {
} }
} }
if (fakeDetails.serverCert != null) { // Poll again, possibly with the pinned cert, to get accurate pairing information.
// Poll again with the pinned cert to get accurate pairing information // This will insert the host into the database too.
runPoll(fakeDetails, true, 0); runPoll(fakeDetails, true, 0);
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
return false; return false;
@@ -457,8 +457,12 @@ public class ComputerManagerService extends Service {
ComputerDetails newDetails = http.getComputerDetails(); ComputerDetails newDetails = http.getComputerDetails();
// Check if this is the PC we expected // Check if this is the PC we expected
if (details.uuid != null && newDetails.uuid != null && if (newDetails.uuid == null) {
!details.uuid.equals(newDetails.uuid)) { 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! // We got the wrong PC!
LimeLog.info("Polling returned the wrong PC!"); LimeLog.info("Polling returned the wrong PC!");
return null; return null;
@@ -468,7 +472,8 @@ public class ComputerManagerService extends Service {
newDetails.activeAddress = address; newDetails.activeAddress = address;
return newDetails; return newDetails;
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
e.printStackTrace();
return null; return null;
} }
} }
@@ -697,8 +702,9 @@ public class ComputerManagerService extends Service {
public void run() { public void run() {
int emptyAppListResponses = 0; int emptyAppListResponses = 0;
do { do {
// Can't poll if it's not online // Can't poll if it's not online or paired
if (computer.state != ComputerDetails.State.ONLINE) { if (computer.state != ComputerDetails.State.ONLINE ||
computer.pairState != PairingManager.PairState.PAIRED) {
if (listener != null) { if (listener != null) {
listener.notifyComputerUpdated(computer); listener.notifyComputerUpdated(computer);
} }
@@ -738,12 +744,12 @@ public class ComputerManagerService extends Service {
// in a row, we'll go ahead and believe it. // in a row, we'll go ahead and believe it.
emptyAppListResponses++; emptyAppListResponses++;
} }
if (appList != null && !appList.isEmpty() && if (!appList.isEmpty() &&
(!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) { (!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) {
// Open the cache file // Open the cache file
OutputStream cacheOut = null; OutputStream cacheOut = null;
try { try {
cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid.toString()); cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid);
CacheHelper.writeStringToOutputStream(cacheOut, appList); CacheHelper.writeStringToOutputStream(cacheOut, appList);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -770,7 +776,7 @@ public class ComputerManagerService extends Service {
listener.notifyComputerUpdated(computer); listener.notifyComputerUpdated(computer);
} }
} }
else if (appList == null || appList.isEmpty()) { else if (appList.isEmpty()) {
LimeLog.warning("Null app list received from "+computer.uuid); LimeLog.warning("Null app list received from "+computer.uuid);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -12,7 +12,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID;
public class LegacyDatabaseReader { public class LegacyDatabaseReader {
private static final String COMPUTER_DB_NAME = "computers.db"; private static final String COMPUTER_DB_NAME = "computers.db";
@@ -24,14 +23,7 @@ public class LegacyDatabaseReader {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
details.name = c.getString(0); details.name = c.getString(0);
details.uuid = c.getString(1);
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);
}
// An earlier schema defined addresses as byte blobs. We'll // An earlier schema defined addresses as byte blobs. We'll
// gracefully migrate those to strings so we can store DNS names // 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.PcView;
import com.limelight.R; import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.PairingManager;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@@ -77,6 +78,14 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
overlayView.setAlpha(0.4f); overlayView.setAlpha(0.4f);
return true; 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; return false;
} }
} }
@@ -39,7 +39,7 @@ public class DiskAssetLoader {
} }
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) { 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 // 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) { 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 // Don't bother with anything if it doesn't exist
if (!file.exists()) { if (!file.exists()) {
@@ -137,7 +137,7 @@ public class DiskAssetLoader {
OutputStream out = null; OutputStream out = null;
boolean success = false; boolean success = false;
try { 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); CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
success = true; success = true;
} catch (IOException e) { } catch (IOException e) {
@@ -151,7 +151,7 @@ public class DiskAssetLoader {
if (!success) { if (!success) {
LimeLog.warning("Unable to populate cache with tuple: "+tuple); 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) { 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) { public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
@@ -91,14 +91,20 @@ public class AddComputerManually extends Activity {
} }
private void doAddPc(String host) { private void doAddPc(String host) {
String msg;
boolean wrongSiteLocal = false; boolean wrongSiteLocal = false;
boolean success; boolean success;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc), SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
getResources().getString(R.string.msg_add_pc), false); 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){ if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host); wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
} }
@@ -3,7 +3,6 @@ package com.limelight.preferences;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.preference.DialogPreference; import android.preference.DialogPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -84,6 +84,9 @@ public class PreferenceConfiguration {
if (resString.equalsIgnoreCase("360p")) { if (resString.equalsIgnoreCase("360p")) {
return 360; return 360;
} }
else if (resString.equalsIgnoreCase("480p")) {
return 480;
}
else if (resString.equalsIgnoreCase("720p")) { else if (resString.equalsIgnoreCase("720p")) {
return 720; return 720;
} }
@@ -103,13 +106,22 @@ public class PreferenceConfiguration {
} }
private static int getWidthFromResolutionString(String resString) { 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) { private static String getResolutionString(int width, int height) {
switch (height) { switch (height) {
case 360: case 360:
return "360p"; return "360p";
case 480:
return "480p";
default: default:
case 720: case 720:
return "720p"; return "720p";
@@ -139,6 +151,9 @@ public class PreferenceConfiguration {
if (width * height <= 640 * 360) { if (width * height <= 640 * 360) {
return (int)(1000 * (fps / 30.0)); return (int)(1000 * (fps / 30.0));
} }
else if (width * height <= 854 * 480) {
return (int)(1500 * (fps / 30.0));
}
// This covers 1280x720 and 1280x800 too // This covers 1280x720 and 1280x800 too
else if (width * height <= 1366 * 768) { else if (width * height <= 1366 * 768) {
return (int)(5000 * (fps / 30.0)); return (int)(5000 * (fps / 30.0));
@@ -13,7 +13,10 @@ import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.cert.CertificateEncodingException; 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_ID, app.getAppId());
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported()); intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); 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); intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
try { try {
if (computer.serverCert != null) { if (computer.serverCert != null) {
@@ -84,8 +87,9 @@ public class ServerHelper {
message = parent.getResources().getString(R.string.error_unknown_host); message = parent.getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = parent.getResources().getString(R.string.error_404); message = parent.getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (IOException | XmlPullParserException e) {
message = e.getMessage(); message = e.getMessage();
e.printStackTrace();
} finally { } finally {
if (onComplete != null) { if (onComplete != null) {
onComplete.run(); onComplete.run();
@@ -125,7 +125,7 @@ public class ShortcutHelper {
} }
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) { 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) @TargetApi(Build.VERSION_CODES.O)
@@ -158,7 +158,7 @@ public class ShortcutHelper {
} }
public boolean createPinnedGameShortcut(String id, Bitmap iconBits, ComputerDetails cDetails, NvApp app) { 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) { public void disableShortcut(String id, CharSequence reason) {
+5
View File
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" 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"/>
</vector>
+2
View File
@@ -2,6 +2,7 @@
<resources> <resources>
<string-array name="resolution_names"> <string-array name="resolution_names">
<item>360p</item> <item>360p</item>
<item>480p</item>
<item>720p</item> <item>720p</item>
<item>1080p</item> <item>1080p</item>
<item>1440p</item> <item>1440p</item>
@@ -9,6 +10,7 @@
</string-array> </string-array>
<string-array name="resolution_values" translatable="false"> <string-array name="resolution_values" translatable="false">
<item>360p</item> <item>360p</item>
<item>480p</item>
<item>720p</item> <item>720p</item>
<item>1080p</item> <item>1080p</item>
<item>1440p</item> <item>1440p</item>
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:3.3.1'
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue May 08 18:56:31 PDT 2018 #Tue Feb 05 20:54:22 PST 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip