Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce01223683 | |||
| e7501a488d | |||
| 5626e9663b | |||
| 01b35ccdd3 | |||
| e83bc747c8 | |||
| cbe4af7623 | |||
| fc9e45270a | |||
| 94c1fc2b66 | |||
| 49999634c1 | |||
| 09f4827d02 | |||
| 52e4e81e35 | |||
| 56b752f63f | |||
| 2e6e835a8e | |||
| d8b0a0ffb5 | |||
| b82d74474a | |||
| 508b855e36 | |||
| 5efcd606e3 | |||
| 3524cdd764 | |||
| 368cd8808d | |||
| b52a6ce93c | |||
| 7ab4e5d0a5 | |||
| 095dfd8035 | |||
| f7c33ef975 | |||
| 57b0bce5a4 | |||
| 7a017d7b97 |
+3
-2
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.limelight"
|
package="com.limelight"
|
||||||
android:versionCode="11"
|
android:versionCode="16"
|
||||||
android:versionName="2.1.5" >
|
android:versionName="2.2.1.2" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="16"
|
android:minSdkVersion="16"
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="tv.ouya.intent.category.APP" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
Limelight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
Limelight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||||
We reverse engineered the Shield streaming software, and created a version that can be run on any Android device.
|
We reverse engineered the Shield streaming software, and created a version that can be run on any Android device.
|
||||||
|
|
||||||
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device on the same network.
|
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device,
|
||||||
|
in your own home, or over the internet.
|
||||||
Streaming can be done remotely using the [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481)
|
|
||||||
application.
|
|
||||||
|
|
||||||
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows Phone](https://github.com/limelight-stream/limelight-wp) are also in development.
|
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows Phone](https://github.com/limelight-stream/limelight-wp) are also in development.
|
||||||
|
|
||||||
@@ -36,9 +34,10 @@ application.
|
|||||||
|
|
||||||
##Usage
|
##Usage
|
||||||
|
|
||||||
* Ensure your Android device and your PC are on the same network or you're running [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481).
|
|
||||||
* Turn on Shield Streaming in the GFE settings
|
* Turn on Shield Streaming in the GFE settings
|
||||||
* In Limelight, enter your PC's IP or Hostname and click "Pair".
|
* If you are connecting from outside the same network, turn on internet
|
||||||
|
streaming
|
||||||
|
* In Limelight, enter your PC's IP or Hostname and click "Pair"
|
||||||
* Accept the pairing confirmation on your PC
|
* Accept the pairing confirmation on your PC
|
||||||
* In Limelight, click "Start Streaming"
|
* In Limelight, click "Start Streaming"
|
||||||
* Play games!
|
* Play games!
|
||||||
|
|||||||
+15
-10
@@ -32,21 +32,26 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
|||||||
public static final int activity_vertical_margin=0x7f050001;
|
public static final int activity_vertical_margin=0x7f050001;
|
||||||
}
|
}
|
||||||
public static final class drawable {
|
public static final class drawable {
|
||||||
public static final int ic_launcher=0x7f020000;
|
public static final int app_icon=0x7f020000;
|
||||||
|
public static final int ic_launcher=0x7f020001;
|
||||||
|
public static final int ouya_icon=0x7f020002;
|
||||||
}
|
}
|
||||||
public static final class id {
|
public static final class id {
|
||||||
public static final int autoDec=0x7f080005;
|
public static final int autoDec=0x7f080006;
|
||||||
public static final int config1080p30Selected=0x7f080009;
|
public static final int bitrateLabel=0x7f08000c;
|
||||||
public static final int config1080p60Selected=0x7f08000a;
|
public static final int bitrateSeekBar=0x7f08000d;
|
||||||
public static final int config720p30Selected=0x7f080007;
|
public static final int config1080p30Selected=0x7f08000a;
|
||||||
public static final int config720p60Selected=0x7f080008;
|
public static final int config1080p60Selected=0x7f08000b;
|
||||||
public static final int hardwareDec=0x7f080006;
|
public static final int config720p30Selected=0x7f080008;
|
||||||
|
public static final int config720p60Selected=0x7f080009;
|
||||||
|
public static final int decoderConfigGroup=0x7f080003;
|
||||||
|
public static final int hardwareDec=0x7f080007;
|
||||||
public static final int hostTextView=0x7f080000;
|
public static final int hostTextView=0x7f080000;
|
||||||
public static final int pairButton=0x7f080002;
|
public static final int pairButton=0x7f080002;
|
||||||
public static final int softwareDec=0x7f080004;
|
public static final int softwareDec=0x7f080005;
|
||||||
public static final int statusButton=0x7f080001;
|
public static final int statusButton=0x7f080001;
|
||||||
public static final int streamConfigGroup=0x7f080003;
|
public static final int streamConfigGroup=0x7f080004;
|
||||||
public static final int surfaceView=0x7f08000b;
|
public static final int surfaceView=0x7f08000e;
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int activity_connection=0x7f030000;
|
public static final int activity_connection=0x7f030000;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -5,7 +5,7 @@
|
|||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="10dp"
|
||||||
tools:context=".Connection" >
|
tools:context=".Connection" >
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
@@ -45,12 +45,13 @@
|
|||||||
android:text="Pair with PC" />
|
android:text="Pair with PC" />
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
|
android:id="@+id/decoderConfigGroup"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_below="@+id/streamConfigGroup"
|
android:layout_below="@+id/streamConfigGroup"
|
||||||
android:layout_marginTop="25dp"
|
android:layout_marginTop="15dp"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_below="@+id/pairButton"
|
android:layout_below="@+id/pairButton"
|
||||||
android:layout_marginTop="25dp"
|
android:layout_marginTop="10dp"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
@@ -92,24 +93,42 @@
|
|||||||
android:id="@+id/config720p60Selected"
|
android:id="@+id/config720p60Selected"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="7dp"
|
||||||
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/config1080p30Selected"
|
android:id="@+id/config1080p30Selected"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="7dp"
|
||||||
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/config1080p60Selected"
|
android:id="@+id/config1080p60Selected"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="7dp"
|
||||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/bitrateSeekBar"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup"
|
||||||
|
android:layout_toLeftOf="@+id/bitrateLabel" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
package com.limelight;
|
package com.limelight;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.nvstream.http.PairingManager;
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -20,6 +18,8 @@ import android.widget.Button;
|
|||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -33,6 +33,8 @@ public class Connection extends Activity {
|
|||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
||||||
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
||||||
|
private SeekBar bitrateSlider;
|
||||||
|
private TextView bitrateLabel;
|
||||||
|
|
||||||
private static final String DEFAULT_HOST = "";
|
private static final String DEFAULT_HOST = "";
|
||||||
public static final String HOST_KEY = "hostText";
|
public static final String HOST_KEY = "hostText";
|
||||||
@@ -42,11 +44,19 @@ public class Connection extends Activity {
|
|||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
||||||
|
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -66,6 +76,8 @@ public class Connection extends Activity {
|
|||||||
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
||||||
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
||||||
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
||||||
|
this.bitrateLabel = (TextView) findViewById(R.id.bitrateLabel);
|
||||||
|
this.bitrateSlider = (SeekBar) findViewById(R.id.bitrateSeekBar);
|
||||||
|
|
||||||
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
||||||
@@ -73,6 +85,10 @@ public class Connection extends Activity {
|
|||||||
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
||||||
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
||||||
|
|
||||||
|
bitrateSlider.setMax(Game.BITRATE_CEILING);
|
||||||
|
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
||||||
|
updateBitrateLabel();
|
||||||
|
|
||||||
rbutton720p30.setChecked(false);
|
rbutton720p30.setChecked(false);
|
||||||
rbutton720p60.setChecked(false);
|
rbutton720p60.setChecked(false);
|
||||||
rbutton1080p30.setChecked(false);
|
rbutton1080p30.setChecked(false);
|
||||||
@@ -124,22 +140,30 @@ public class Connection extends Activity {
|
|||||||
if (buttonView == rbutton720p30) {
|
if (buttonView == rbutton720p30) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_30);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton720p60) {
|
else if (buttonView == rbutton720p60) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_60);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton1080p30) {
|
else if (buttonView == rbutton1080p30) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_30);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton1080p60) {
|
else if (buttonView == rbutton1080p60) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_60);
|
||||||
}
|
}
|
||||||
else if (buttonView == forceSoftDec) {
|
else if (buttonView == forceSoftDec) {
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
||||||
@@ -160,6 +184,45 @@ public class Connection extends Activity {
|
|||||||
forceHardDec.setOnCheckedChangeListener(occl);
|
forceHardDec.setOnCheckedChangeListener(occl);
|
||||||
autoDec.setOnCheckedChangeListener(occl);
|
autoDec.setOnCheckedChangeListener(occl);
|
||||||
|
|
||||||
|
this.bitrateSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||||
|
boolean fromUser) {
|
||||||
|
|
||||||
|
// Verify the user's selection
|
||||||
|
if (fromUser) {
|
||||||
|
int floor;
|
||||||
|
if (rbutton720p30.isChecked()) {
|
||||||
|
floor = Game.BITRATE_FLOOR_720_30;
|
||||||
|
}
|
||||||
|
else if (rbutton720p60.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_720_60;
|
||||||
|
}
|
||||||
|
else if (rbutton1080p30.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_30;
|
||||||
|
}
|
||||||
|
else /*if (rbutton1080p60.isChecked())*/ {
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < floor) {
|
||||||
|
seekBar.setProgress(floor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBitrateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.statusButton.setOnClickListener(new OnClickListener() {
|
this.statusButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View arg0) {
|
public void onClick(View arg0) {
|
||||||
@@ -168,6 +231,12 @@ public class Connection extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that the bitrate preference is up to date before
|
||||||
|
// starting the game activity
|
||||||
|
prefs.edit().
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress()).
|
||||||
|
commit();
|
||||||
|
|
||||||
Intent intent = new Intent(Connection.this, Game.class);
|
Intent intent = new Intent(Connection.this, Game.class);
|
||||||
intent.putExtra("host", Connection.this.hostText.getText().toString());
|
intent.putExtra("host", Connection.this.hostText.getText().toString());
|
||||||
Connection.this.startActivity(intent);
|
Connection.this.startActivity(intent);
|
||||||
@@ -177,6 +246,11 @@ public class Connection extends Activity {
|
|||||||
this.pairButton.setOnClickListener(new OnClickListener() {
|
this.pairButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View arg0) {
|
public void onClick(View arg0) {
|
||||||
|
if (Connection.this.hostText.getText().length() == 0) {
|
||||||
|
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
|
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -198,29 +272,39 @@ public class Connection extends Activity {
|
|||||||
String message;
|
String message;
|
||||||
try {
|
try {
|
||||||
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
|
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
|
||||||
macAddress, PlatformBinding.getDeviceName());
|
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider(Connection.this));
|
||||||
try {
|
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||||
if (httpConn.getPairState()) {
|
message = "Already paired";
|
||||||
message = "Already paired";
|
}
|
||||||
|
else {
|
||||||
|
final String pinStr = PairingManager.generatePinString();
|
||||||
|
|
||||||
|
// Spin the dialog off in a thread because it blocks
|
||||||
|
Dialog.displayDialog(Connection.this, "Pairing", "Please enter the following PIN on the target PC: "+pinStr, false);
|
||||||
|
|
||||||
|
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
||||||
|
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
||||||
|
message = "Incorrect PIN";
|
||||||
|
}
|
||||||
|
else if (pairState == PairingManager.PairState.FAILED) {
|
||||||
|
message = "Pairing failed";
|
||||||
|
}
|
||||||
|
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||||
|
message = "Paired successfully";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int session = httpConn.getSessionId();
|
// Should be no other values
|
||||||
if (session == 0) {
|
message = null;
|
||||||
message = "Pairing was declined by the target";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Pairing was successful";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
}
|
||||||
} catch (UnknownHostException e1) {
|
} catch (UnknownHostException e1) {
|
||||||
message = "Failed to resolve host";
|
message = "Failed to resolve host";
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
final String toastMessage = message;
|
final String toastMessage = message;
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -235,4 +319,7 @@ public class Connection extends Activity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBitrateLabel() {
|
||||||
|
bitrateLabel.setText("Max Bitrate: "+bitrateSlider.getProgress()+" Mbps");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-10
@@ -20,6 +20,7 @@ import android.graphics.Point;
|
|||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -63,11 +64,25 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
public static final String HEIGHT_PREF_STRING = "ResV";
|
public static final String HEIGHT_PREF_STRING = "ResV";
|
||||||
public static final String REFRESH_RATE_PREF_STRING = "FPS";
|
public static final String REFRESH_RATE_PREF_STRING = "FPS";
|
||||||
public static final String DECODER_PREF_STRING = "Decoder";
|
public static final String DECODER_PREF_STRING = "Decoder";
|
||||||
|
public static final String BITRATE_PREF_STRING = "Bitrate";
|
||||||
|
|
||||||
|
public static final int BITRATE_FLOOR_720_30 = 2;
|
||||||
|
public static final int BITRATE_FLOOR_720_60 = 4;
|
||||||
|
public static final int BITRATE_FLOOR_1080_30 = 4;
|
||||||
|
public static final int BITRATE_FLOOR_1080_60 = 10;
|
||||||
|
|
||||||
|
public static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
|
public static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
|
public static final int BITRATE_DEFAULT_1080_30 = 10;
|
||||||
|
public static final int BITRATE_DEFAULT_1080_60 = 30;
|
||||||
|
|
||||||
|
public static final int BITRATE_CEILING = 50;
|
||||||
|
|
||||||
public static final int DEFAULT_WIDTH = 1280;
|
public static final int DEFAULT_WIDTH = 1280;
|
||||||
public static final int DEFAULT_HEIGHT = 720;
|
public static final int DEFAULT_HEIGHT = 720;
|
||||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||||
public static final int DEFAULT_DECODER = 0;
|
public static final int DEFAULT_DECODER = 0;
|
||||||
|
public static final int DEFAULT_BITRATE = BITRATE_DEFAULT_720_60;
|
||||||
|
|
||||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||||
public static final int AUTOSELECT_DECODER = 0;
|
public static final int AUTOSELECT_DECODER = 0;
|
||||||
@@ -77,15 +92,24 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// We don't want a title bar
|
||||||
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
// Full-screen and don't let the display go off
|
// Full-screen and don't let the display go off
|
||||||
getWindow().setFlags(
|
getWindow().addFlags(
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
|
||||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
|
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
||||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
// We don't want a title bar
|
// If we're going to use immersive mode, we want to have
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
// the entire screen
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
|
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
// Change volume button behavior
|
// Change volume button behavior
|
||||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
@@ -116,10 +140,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int refreshRate;
|
int refreshRate, bitrate;
|
||||||
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
|
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
|
||||||
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
|
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
|
||||||
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
|
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
|
||||||
|
bitrate = prefs.getInt(BITRATE_PREF_STRING, DEFAULT_BITRATE);
|
||||||
sh.setFixedSize(width, height);
|
sh.setFixedSize(width, height);
|
||||||
|
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
@@ -130,7 +155,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
|
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
|
||||||
new StreamConfiguration(width, height, refreshRate));
|
new StreamConfiguration(width, height, refreshRate, bitrate * 1000), PlatformBinding.getCryptoProvider(this));
|
||||||
keybTranslator = new KeyboardTranslator(conn);
|
keybTranslator = new KeyboardTranslator(conn);
|
||||||
controllerHandler = new ControllerHandler(conn);
|
controllerHandler = new ControllerHandler(conn);
|
||||||
|
|
||||||
@@ -147,8 +172,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private void hideSystemUi() {
|
private Runnable hideSystemUi = new Runnable() {
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Use immersive mode on 4.4+ or standard low profile on previous builds
|
// Use immersive mode on 4.4+ or standard low profile on previous builds
|
||||||
@@ -167,7 +191,14 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
private void hideSystemUi() {
|
||||||
|
Handler h = getWindow().getDecorView().getHandler();
|
||||||
|
if (h != null) {
|
||||||
|
h.removeCallbacks(hideSystemUi);
|
||||||
|
h.postDelayed(hideSystemUi, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -220,6 +251,16 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
// Pressing a volume button drops the immersive flag so the UI shows up again and doesn't
|
||||||
|
// go away. I'm not sure if that's a bug or a feature, but we're working around it here
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
Handler h = getWindow().getDecorView().getHandler();
|
||||||
|
if (h != null) {
|
||||||
|
h.removeCallbacks(hideSystemUi);
|
||||||
|
h.postDelayed(hideSystemUi, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (event.getDevice() != null &&
|
if (event.getDevice() != null &&
|
||||||
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
|
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
|
||||||
short translated = keybTranslator.translate(event.getKeyCode());
|
short translated = keybTranslator.translate(event.getKeyCode());
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.limelight.binding;
|
package com.limelight.binding;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import com.limelight.binding.audio.AndroidAudioRenderer;
|
import com.limelight.binding.audio.AndroidAudioRenderer;
|
||||||
|
import com.limelight.binding.crypto.AndroidCryptoProvider;
|
||||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||||
|
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||||
|
|
||||||
public class PlatformBinding {
|
public class PlatformBinding {
|
||||||
public static String getDeviceName() {
|
public static String getDeviceName() {
|
||||||
@@ -13,4 +17,8 @@ public class PlatformBinding {
|
|||||||
public static AudioRenderer getAudioRenderer() {
|
public static AudioRenderer getAudioRenderer() {
|
||||||
return new AndroidAudioRenderer();
|
return new AndroidAudioRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LimelightCryptoProvider getCryptoProvider(Context c) {
|
||||||
|
return new AndroidCryptoProvider(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package com.limelight.binding.crypto;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
|
import org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||||
|
|
||||||
|
private File certFile;
|
||||||
|
private File keyFile;
|
||||||
|
|
||||||
|
private X509Certificate cert;
|
||||||
|
private RSAPrivateKey key;
|
||||||
|
private byte[] pemCertBytes;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Install the Bouncy Castle provider
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AndroidCryptoProvider(Context c) {
|
||||||
|
String dataPath = c.getFilesDir().getAbsolutePath();
|
||||||
|
|
||||||
|
certFile = new File(dataPath + File.separator + "client.crt");
|
||||||
|
keyFile = new File(dataPath + File.separator + "client.key");
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] loadFileToBytes(File f) {
|
||||||
|
if (!f.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileInputStream fin = new FileInputStream(f);
|
||||||
|
byte[] fileData = new byte[(int) f.length()];
|
||||||
|
fin.read(fileData);
|
||||||
|
fin.close();
|
||||||
|
return fileData;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean loadCertKeyPair() {
|
||||||
|
byte[] certBytes = loadFileToBytes(certFile);
|
||||||
|
byte[] keyBytes = loadFileToBytes(keyFile);
|
||||||
|
|
||||||
|
// If either file was missing, we definitely can't succeed
|
||||||
|
if (certBytes == null || keyBytes == null) {
|
||||||
|
LimeLog.info("Missing cert or key; need to generate a new one");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
|
||||||
|
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||||
|
pemCertBytes = certBytes;
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
|
||||||
|
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
// May happen if the cert is corrupt
|
||||||
|
LimeLog.warning("Corrupted certificate");
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// Should never happen
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
// May happen if the key is corrupt
|
||||||
|
LimeLog.warning("Corrupted key");
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
// Should never happen
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Loaded key pair from disk");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrulyRandom")
|
||||||
|
private boolean generateCertKeyPair() {
|
||||||
|
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
||||||
|
X500Principal principalName = new X500Principal("CN=NVIDIA GameStream Client");
|
||||||
|
|
||||||
|
byte[] snBytes = new byte[8];
|
||||||
|
new SecureRandom().nextBytes(snBytes);
|
||||||
|
|
||||||
|
KeyPair keyPair;
|
||||||
|
try {
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||||
|
keyPairGenerator.initialize(2048);
|
||||||
|
keyPair = keyPairGenerator.generateKeyPair();
|
||||||
|
} catch (NoSuchAlgorithmException e1) {
|
||||||
|
// Should never happen
|
||||||
|
e1.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
// Should never happen
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
Date expirationDate = new Date();
|
||||||
|
|
||||||
|
// Expires in 20 years
|
||||||
|
expirationDate.setYear(expirationDate.getYear() + 20);
|
||||||
|
|
||||||
|
certGenerator.setSerialNumber(new BigInteger(snBytes).abs());
|
||||||
|
certGenerator.setIssuerDN(principalName);
|
||||||
|
certGenerator.setNotBefore(now);
|
||||||
|
certGenerator.setNotAfter(expirationDate);
|
||||||
|
certGenerator.setSubjectDN(principalName);
|
||||||
|
certGenerator.setPublicKey(keyPair.getPublic());
|
||||||
|
certGenerator.setSignatureAlgorithm("SHA1withRSA");
|
||||||
|
|
||||||
|
try {
|
||||||
|
cert = certGenerator.generate(keyPair.getPrivate(), "BC");
|
||||||
|
key = (RSAPrivateKey) keyPair.getPrivate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Nothing should go wrong here
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Generated a new key pair");
|
||||||
|
|
||||||
|
// Save the resulting pair
|
||||||
|
saveCertKeyPair();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCertKeyPair() {
|
||||||
|
try {
|
||||||
|
FileOutputStream certOut = new FileOutputStream(certFile);
|
||||||
|
FileOutputStream keyOut = new FileOutputStream(keyFile);
|
||||||
|
|
||||||
|
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||||
|
StringWriter strWriter = new StringWriter();
|
||||||
|
PEMWriter pemWriter = new PEMWriter(strWriter);
|
||||||
|
pemWriter.writeObject(cert);
|
||||||
|
pemWriter.close();
|
||||||
|
|
||||||
|
// Line endings MUST be UNIX for the PC to accept the cert properly
|
||||||
|
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
|
||||||
|
String pemStr = strWriter.getBuffer().toString();
|
||||||
|
for (int i = 0; i < pemStr.length(); i++) {
|
||||||
|
char c = pemStr.charAt(i);
|
||||||
|
if (c != '\r')
|
||||||
|
certWriter.append(c);
|
||||||
|
}
|
||||||
|
certWriter.close();
|
||||||
|
|
||||||
|
// Write the private out in PKCS8 format
|
||||||
|
keyOut.write(key.getEncoded());
|
||||||
|
|
||||||
|
certOut.close();
|
||||||
|
keyOut.close();
|
||||||
|
|
||||||
|
LimeLog.info("Saved generated key pair to disk");
|
||||||
|
} catch (IOException e) {
|
||||||
|
// This isn't good because it means we'll have
|
||||||
|
// to re-pair next time
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate getClientCertificate() {
|
||||||
|
// Use a lock here to ensure only one guy will be generating or loading
|
||||||
|
// the certificate and key at a time
|
||||||
|
synchronized (this) {
|
||||||
|
// Return a loaded cert if we have one
|
||||||
|
if (cert != null) {
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No loaded cert yet, let's see if we have one on disk
|
||||||
|
if (loadCertKeyPair()) {
|
||||||
|
// Got one
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to generate a new key pair
|
||||||
|
if (!generateCertKeyPair()) {
|
||||||
|
// Failed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the generated pair
|
||||||
|
loadCertKeyPair();
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RSAPrivateKey getClientPrivateKey() {
|
||||||
|
// Use a lock here to ensure only one guy will be generating or loading
|
||||||
|
// the certificate and key at a time
|
||||||
|
synchronized (this) {
|
||||||
|
// Return a loaded key if we have one
|
||||||
|
if (key != null) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No loaded key yet, let's see if we have one on disk
|
||||||
|
if (loadCertKeyPair()) {
|
||||||
|
// Got one
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to generate a new key pair
|
||||||
|
if (!generateCertKeyPair()) {
|
||||||
|
// Failed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the generated pair
|
||||||
|
loadCertKeyPair();
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPemEncodedClientCertificate() {
|
||||||
|
synchronized (this) {
|
||||||
|
// Call our helper function to do the cert loading/generation for us
|
||||||
|
getClientCertificate();
|
||||||
|
|
||||||
|
// Return a cached value if we have it
|
||||||
|
return pemCertBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encodeBase64String(byte[] data) {
|
||||||
|
return Base64.encodeToString(data, Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.limelight.binding.input;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -17,6 +18,24 @@ public class ControllerHandler {
|
|||||||
private short rightStickY = 0x0000;
|
private short rightStickY = 0x0000;
|
||||||
private short leftStickX = 0x0000;
|
private short leftStickX = 0x0000;
|
||||||
private short leftStickY = 0x0000;
|
private short leftStickY = 0x0000;
|
||||||
|
private int emulatingButtonFlags = 0;
|
||||||
|
|
||||||
|
// Used for OUYA bumper state tracking since they force all buttons
|
||||||
|
// up when the OUYA button goes down. We watch the last time we get
|
||||||
|
// a bumper up and compare that to our maximum delay when we receive
|
||||||
|
// a Start button press to see if we should activate one of our
|
||||||
|
// emulated button combos.
|
||||||
|
private long lastLbUpTime = 0;
|
||||||
|
private long lastRbUpTime = 0;
|
||||||
|
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||||
|
|
||||||
|
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 10;
|
||||||
|
|
||||||
|
private static final int EMULATING_SPECIAL = 0x1;
|
||||||
|
private static final int EMULATING_SELECT = 0x2;
|
||||||
|
|
||||||
|
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
|
||||||
|
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||||
|
|
||||||
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
|
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
|
||||||
|
|
||||||
@@ -298,6 +317,18 @@ public class ControllerHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the button hasn't been down long enough, sleep for a bit before sending the up event
|
||||||
|
// This allows "instant" button presses (like OUYA's virtual menu button) to work. This
|
||||||
|
// path should not be triggered during normal usage.
|
||||||
|
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
|
||||||
|
{
|
||||||
|
// Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
|
||||||
|
// UI thread.
|
||||||
|
try {
|
||||||
|
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||||
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||||
@@ -325,6 +356,7 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_B:
|
case KeyEvent.KEYCODE_BUTTON_B:
|
||||||
inputMap &= ~ControllerPacket.B_FLAG;
|
inputMap &= ~ControllerPacket.B_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
case KeyEvent.KEYCODE_BUTTON_A:
|
case KeyEvent.KEYCODE_BUTTON_A:
|
||||||
inputMap &= ~ControllerPacket.A_FLAG;
|
inputMap &= ~ControllerPacket.A_FLAG;
|
||||||
break;
|
break;
|
||||||
@@ -336,9 +368,11 @@ public class ControllerHandler {
|
|||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||||
inputMap &= ~ControllerPacket.LB_FLAG;
|
inputMap &= ~ControllerPacket.LB_FLAG;
|
||||||
|
lastLbUpTime = SystemClock.uptimeMillis();
|
||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||||
inputMap &= ~ControllerPacket.RB_FLAG;
|
inputMap &= ~ControllerPacket.RB_FLAG;
|
||||||
|
lastRbUpTime = SystemClock.uptimeMillis();
|
||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||||
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||||
@@ -356,11 +390,39 @@ public class ControllerHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If one of the two is up, the special button comes up too
|
// Check if we're emulating the select button
|
||||||
if ((inputMap & ControllerPacket.BACK_FLAG) == 0 ||
|
if ((emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
|
||||||
(inputMap & ControllerPacket.PLAY_FLAG) == 0)
|
|
||||||
{
|
{
|
||||||
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
// If either start or LB is up, select comes up too
|
||||||
|
if ((inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||||
|
(inputMap & ControllerPacket.LB_FLAG) == 0)
|
||||||
|
{
|
||||||
|
inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||||
|
|
||||||
|
emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're emulating the special button
|
||||||
|
if ((emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
|
||||||
|
{
|
||||||
|
// If either start or select and RB is up, the special button comes up too
|
||||||
|
if ((inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||||
|
((inputMap & ControllerPacket.BACK_FLAG) == 0 &&
|
||||||
|
(inputMap & ControllerPacket.RB_FLAG) == 0))
|
||||||
|
{
|
||||||
|
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||||
|
|
||||||
|
emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendControllerInputPacket();
|
sendControllerInputPacket();
|
||||||
@@ -405,6 +467,7 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_B:
|
case KeyEvent.KEYCODE_BUTTON_B:
|
||||||
inputMap |= ControllerPacket.B_FLAG;
|
inputMap |= ControllerPacket.B_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
case KeyEvent.KEYCODE_BUTTON_A:
|
case KeyEvent.KEYCODE_BUTTON_A:
|
||||||
inputMap |= ControllerPacket.A_FLAG;
|
inputMap |= ControllerPacket.A_FLAG;
|
||||||
break;
|
break;
|
||||||
@@ -436,12 +499,27 @@ public class ControllerHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We detect back+start as the special button combo
|
// Start+LB acts like select for controllers with one button
|
||||||
if ((inputMap & ControllerPacket.BACK_FLAG) != 0 &&
|
if ((inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
|
||||||
|
((inputMap & ControllerPacket.LB_FLAG) != 0 ||
|
||||||
|
SystemClock.uptimeMillis() - lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||||
|
{
|
||||||
|
inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
|
||||||
|
inputMap |= ControllerPacket.BACK_FLAG;
|
||||||
|
|
||||||
|
emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We detect select+start or start+RB as the special button combo
|
||||||
|
if (((inputMap & ControllerPacket.RB_FLAG) != 0 ||
|
||||||
|
(SystemClock.uptimeMillis() - lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) ||
|
||||||
|
(inputMap & ControllerPacket.BACK_FLAG) != 0) &&
|
||||||
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
||||||
{
|
{
|
||||||
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG);
|
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
|
||||||
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||||
|
|
||||||
|
emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendControllerInputPacket();
|
sendControllerInputPacket();
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ public class Dialog implements Runnable {
|
|||||||
|
|
||||||
public static void closeDialogs()
|
public static void closeDialogs()
|
||||||
{
|
{
|
||||||
for (Dialog d : rundownDialogs)
|
for (Dialog d : rundownDialogs) {
|
||||||
d.alert.dismiss();
|
if (d.alert.isShowing()) {
|
||||||
|
d.alert.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rundownDialogs.clear();
|
rundownDialogs.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
|
|||||||
|
|
||||||
public static void closeDialogs()
|
public static void closeDialogs()
|
||||||
{
|
{
|
||||||
for (SpinnerDialog d : rundownDialogs)
|
for (SpinnerDialog d : rundownDialogs) {
|
||||||
d.progress.dismiss();
|
if (d.progress.isShowing()) {
|
||||||
|
d.progress.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rundownDialogs.clear();
|
rundownDialogs.clear();
|
||||||
}
|
}
|
||||||
@@ -86,7 +89,9 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
progress.dismiss();
|
if (progress.isShowing()) {
|
||||||
|
progress.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user