mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 03:23:07 +00:00
Major update to v2.5
This commit is contained in:
parent
cc37da9a56
commit
3176ee72fe
@ -1,7 +1,7 @@
|
|||||||
<?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="19"
|
android:versionCode="20"
|
||||||
android:versionName="2.5 (alpha)" >
|
android:versionName="2.5 (alpha)" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
@ -21,7 +21,8 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme" >
|
android:theme="@style/AppTheme" >
|
||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.Connection"
|
android:name="com.limelight.PcView"
|
||||||
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -29,6 +30,35 @@
|
|||||||
<category android:name="tv.ouya.intent.category.APP" />
|
<category android:name="tv.ouya.intent.category.APP" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.limelight.AppView"
|
||||||
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
|
android:label="App List" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.limelight.PcView" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.limelight.StreamSettings"
|
||||||
|
android:label="Streaming Settings" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.limelight.PcView" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.limelight.AdvancedSettings"
|
||||||
|
android:label="Advanced Settings" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.limelight.StreamSettings" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.limelight.AddComputerManually"
|
||||||
|
android:label="Add Computer Manually" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.limelight.StreamSettings" />
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.Game"
|
android:name="com.limelight.Game"
|
||||||
android:screenOrientation="sensorLandscape"
|
android:screenOrientation="sensorLandscape"
|
||||||
@ -43,6 +73,9 @@
|
|||||||
<service
|
<service
|
||||||
android:name="com.limelight.discovery.DiscoveryService"
|
android:name="com.limelight.discovery.DiscoveryService"
|
||||||
android:label="mDNS PC Auto-Discovery Service" />
|
android:label="mDNS PC Auto-Discovery Service" />
|
||||||
|
<service
|
||||||
|
android:name="com.limelight.computers.ComputerManagerService"
|
||||||
|
android:label="Computer Management Service" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -34,29 +34,40 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
|||||||
public static final class drawable {
|
public static final class drawable {
|
||||||
public static final int app_icon=0x7f020000;
|
public static final int app_icon=0x7f020000;
|
||||||
public static final int ic_launcher=0x7f020001;
|
public static final int ic_launcher=0x7f020001;
|
||||||
public static final int ouya_icon=0x7f020002;
|
public static final int list_view_border=0x7f020002;
|
||||||
|
public static final int ouya_icon=0x7f020003;
|
||||||
}
|
}
|
||||||
public static final class id {
|
public static final class id {
|
||||||
|
public static final int addPc=0x7f080001;
|
||||||
|
public static final int advancedSettingsButton=0x7f080012;
|
||||||
|
public static final int appListText=0x7f080009;
|
||||||
public static final int autoDec=0x7f080004;
|
public static final int autoDec=0x7f080004;
|
||||||
public static final int bitrateLabel=0x7f08000b;
|
public static final int bitrateLabel=0x7f080006;
|
||||||
public static final int bitrateSeekBar=0x7f08000c;
|
public static final int bitrateSeekBar=0x7f080007;
|
||||||
public static final int config1080p30Selected=0x7f080009;
|
public static final int config1080p30Selected=0x7f080010;
|
||||||
public static final int config1080p60Selected=0x7f08000a;
|
public static final int config1080p60Selected=0x7f080011;
|
||||||
public static final int config720p30Selected=0x7f080007;
|
public static final int config720p30Selected=0x7f08000e;
|
||||||
public static final int config720p60Selected=0x7f080008;
|
public static final int config720p60Selected=0x7f08000f;
|
||||||
public static final int decoderConfigGroup=0x7f080001;
|
public static final int decoderConfigGroup=0x7f080002;
|
||||||
|
public static final int discoveryText=0x7f08000b;
|
||||||
public static final int hardwareDec=0x7f080005;
|
public static final int hardwareDec=0x7f080005;
|
||||||
public static final int hostTextView=0x7f080000;
|
public static final int hostTextView=0x7f080000;
|
||||||
public static final int pairButton=0x7f080006;
|
public static final int manuallyAddPc=0x7f080013;
|
||||||
public static final int quitButton=0x7f08000e;
|
public static final int pcListView=0x7f080008;
|
||||||
|
public static final int rowTextView=0x7f080014;
|
||||||
|
public static final int settingsButton=0x7f08000c;
|
||||||
public static final int softwareDec=0x7f080003;
|
public static final int softwareDec=0x7f080003;
|
||||||
public static final int statusButton=0x7f08000d;
|
public static final int streamConfigGroup=0x7f08000d;
|
||||||
public static final int streamConfigGroup=0x7f080002;
|
public static final int surfaceView=0x7f08000a;
|
||||||
public static final int surfaceView=0x7f08000f;
|
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int activity_connection=0x7f030000;
|
public static final int activity_add_computer_manually=0x7f030000;
|
||||||
public static final int activity_game=0x7f030001;
|
public static final int activity_advanced_settings=0x7f030001;
|
||||||
|
public static final int activity_app_view=0x7f030002;
|
||||||
|
public static final int activity_game=0x7f030003;
|
||||||
|
public static final int activity_pc_view=0x7f030004;
|
||||||
|
public static final int activity_stream_settings=0x7f030005;
|
||||||
|
public static final int simplerow=0x7f030006;
|
||||||
}
|
}
|
||||||
public static final class string {
|
public static final class string {
|
||||||
public static final int app_name=0x7f060000;
|
public static final int app_name=0x7f060000;
|
||||||
|
Binary file not shown.
7
res/drawable/list_view_border.xml
Normal file
7
res/drawable/list_view_border.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle" >
|
||||||
|
|
||||||
|
<stroke android:width="1dip" android:color="#ffffff"/>
|
||||||
|
|
||||||
|
</shape>
|
34
res/layout/activity_add_computer_manually.xml
Normal file
34
res/layout/activity_add_computer_manually.xml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
tools:context=".Connection" >
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/hostTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:ems="10"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:hint="IP address of GeForce PC" >
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</EditText>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/addPc"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/hostTextView"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="Manually Add PC" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
62
res/layout/activity_advanced_settings.xml
Normal file
62
res/layout/activity_advanced_settings.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
tools:context=".Connection" >
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/decoderConfigGroup"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/softwareDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Force Software Decoding" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/autoDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Auto-select Decoder (Recommended)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/hardwareDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Force Hardware Decoding" />
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_marginTop="10dp" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/bitrateSeekBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_toLeftOf="@+id/bitrateLabel" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
36
res/layout/activity_app_view.xml
Normal file
36
res/layout/activity_app_view.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
tools:context=".AppView" >
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:id="@+id/pcListView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/appListText"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:longClickable="false"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:stackFromBottom="false">
|
||||||
|
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/appListText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:text="Applications" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -1,143 +0,0 @@
|
|||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
tools:context=".Connection" >
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content" >
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/hostTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:ems="10"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:inputType="textNoSuggestions"
|
|
||||||
android:hint="IP address of GeForce PC" />
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/decoderConfigGroup"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_below="@+id/streamConfigGroup"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/softwareDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Force Software Decoding" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/autoDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Auto-select Decoder (Recommended)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/hardwareDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Force Hardware Decoding" />
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/streamConfigGroup"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_below="@+id/pairButton"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config720p30Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config720p60Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config1080p30Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config1080p60Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
|
||||||
</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" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/pairButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignRight="@+id/statusButton"
|
|
||||||
android:layout_below="@+id/statusButton"
|
|
||||||
android:layout_marginRight="114dp"
|
|
||||||
android:text="Pair with PC" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/statusButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/hostTextView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="Start Streaming Steam!" >
|
|
||||||
|
|
||||||
<requestFocus />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/quitButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@+id/streamConfigGroup"
|
|
||||||
android:layout_alignLeft="@+id/statusButton"
|
|
||||||
android:layout_marginLeft="122dp"
|
|
||||||
android:text="Quit Steam" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
44
res/layout/activity_pc_view.xml
Normal file
44
res/layout/activity_pc_view.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
tools:context=".PcView" >
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:id="@+id/pcListView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/discoveryText"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:longClickable="false"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:stackFromBottom="false">
|
||||||
|
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/discoveryText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_below="@+id/settingsButton"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:text="Discovered PC List" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/settingsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:text="Streaming Settings" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
72
res/layout/activity_stream_settings.xml
Normal file
72
res/layout/activity_stream_settings.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
tools:context=".Connection" >
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/streamConfigGroup"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config720p30Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config720p60Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config1080p30Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config1080p60Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/advancedSettingsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/streamConfigGroup"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:text="Advanced Settings" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/manuallyAddPc"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/advancedSettingsButton"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="Add PC Manually" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
12
res/layout/simplerow.xml
Normal file
12
res/layout/simplerow.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/rowTextView"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:background="@drawable/list_view_border"
|
||||||
|
android:textIsSelectable="false" >
|
||||||
|
|
||||||
|
</TextView>
|
96
src/com/limelight/AddComputerManually.java
Normal file
96
src/com/limelight/AddComputerManually.java
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import com.limelight.computers.ComputerManagerService;
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class AddComputerManually extends Activity {
|
||||||
|
private Button addPcButton;
|
||||||
|
private TextView hostText;
|
||||||
|
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
public void onServiceConnected(ComponentName className, final IBinder binder) {
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String msg;
|
||||||
|
boolean finish = false;
|
||||||
|
try {
|
||||||
|
InetAddress addr = InetAddress.getByName(hostText.getText().toString());
|
||||||
|
|
||||||
|
if (!((ComputerManagerService.ComputerManagerBinder)binder).addComputerBlocking(addr)){
|
||||||
|
msg = "Unable to connect to the specified computer. Make sure the required ports are allowed through the firewall.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg = "Successfully added computer";
|
||||||
|
finish = true;
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
msg = "Unable to resolve PC address. Make sure you didn't make a typo in the address.";
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean toastFinish = finish;
|
||||||
|
final String toastMsg = msg;
|
||||||
|
AddComputerManually.this.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Unbind from this service
|
||||||
|
unbindService(AddComputerManually.this.serviceConnection);
|
||||||
|
|
||||||
|
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
if (toastFinish) {
|
||||||
|
// Close the activity
|
||||||
|
AddComputerManually.this.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_add_computer_manually);
|
||||||
|
|
||||||
|
this.addPcButton = (Button) findViewById(R.id.addPc);
|
||||||
|
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||||
|
|
||||||
|
addPcButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Toast.makeText(AddComputerManually.this, "Adding PC...", Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
// Bind to the service which will try to add the PC
|
||||||
|
bindService(new Intent(AddComputerManually.this, ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
102
src/com/limelight/AdvancedSettings.java
Normal file
102
src/com/limelight/AdvancedSettings.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
|
||||||
|
public class AdvancedSettings extends Activity {
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
||||||
|
private SeekBar bitrateSlider;
|
||||||
|
private TextView bitrateLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
|
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_advanced_settings);
|
||||||
|
|
||||||
|
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
||||||
|
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
||||||
|
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);
|
||||||
|
|
||||||
|
bitrateSlider.setMax(Game.BITRATE_CEILING);
|
||||||
|
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
||||||
|
updateBitrateLabel();
|
||||||
|
|
||||||
|
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||||
|
case Game.FORCE_SOFTWARE_DECODER:
|
||||||
|
forceSoftDec.setChecked(true);
|
||||||
|
autoDec.setChecked(false);
|
||||||
|
forceHardDec.setChecked(false);
|
||||||
|
break;
|
||||||
|
case Game.AUTOSELECT_DECODER:
|
||||||
|
forceSoftDec.setChecked(false);
|
||||||
|
autoDec.setChecked(true);
|
||||||
|
forceHardDec.setChecked(false);
|
||||||
|
break;
|
||||||
|
case Game.FORCE_HARDWARE_DECODER:
|
||||||
|
forceSoftDec.setChecked(false);
|
||||||
|
autoDec.setChecked(false);
|
||||||
|
forceHardDec.setChecked(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView,
|
||||||
|
boolean isChecked) {
|
||||||
|
if (!isChecked) {
|
||||||
|
// Ignore non-checked buttons
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonView == forceSoftDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == forceHardDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_HARDWARE_DECODER).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == autoDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.AUTOSELECT_DECODER).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
forceSoftDec.setOnCheckedChangeListener(occl);
|
||||||
|
forceHardDec.setOnCheckedChangeListener(occl);
|
||||||
|
autoDec.setOnCheckedChangeListener(occl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBitrateLabel() {
|
||||||
|
bitrateLabel.setText("Max Bitrate: "+bitrateSlider.getProgress()+" Mbps");
|
||||||
|
}
|
||||||
|
}
|
251
src/com/limelight/AppView.java
Normal file
251
src/com/limelight/AppView.java
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||||
|
import com.limelight.nvstream.http.NvApp;
|
||||||
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
import com.limelight.utils.SpinnerDialog;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
|
|
||||||
|
public class AppView extends Activity {
|
||||||
|
private ListView appList;
|
||||||
|
private ArrayAdapter<AppObject> appListAdapter;
|
||||||
|
private InetAddress ipAddress;
|
||||||
|
private String uniqueId;
|
||||||
|
|
||||||
|
private final static int RESUME_ID = 1;
|
||||||
|
private final static int QUIT_ID = 2;
|
||||||
|
|
||||||
|
public final static String ADDRESS_EXTRA = "Address";
|
||||||
|
public final static String UNIQUEID_EXTRA = "UniqueId";
|
||||||
|
public final static String NAME_EXTRA = "Name";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_app_view);
|
||||||
|
|
||||||
|
byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA);
|
||||||
|
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
||||||
|
if (address == null || uniqueId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle("App List for "+getIntent().getStringExtra(NAME_EXTRA));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ipAddress = InetAddress.getByAddress(address);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the list view
|
||||||
|
appList = (ListView)findViewById(R.id.pcListView);
|
||||||
|
appListAdapter = new ArrayAdapter<AppObject>(this, R.layout.simplerow);
|
||||||
|
appListAdapter.setNotifyOnChange(false);
|
||||||
|
appList.setAdapter(appListAdapter);
|
||||||
|
appList.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||||
|
long id) {
|
||||||
|
AppObject app = (AppObject) appListAdapter.getItem(pos);
|
||||||
|
if (app == null || app.app == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only open the context menu if it's running, otherwise start it
|
||||||
|
if (app.app.getIsRunning()) {
|
||||||
|
openContextMenu(arg1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doStart(app.app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerForContextMenu(appList);
|
||||||
|
updateAppList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
SpinnerDialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
updateAppList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
|
||||||
|
menu.add(Menu.NONE, RESUME_ID, 1, "Resume Session");
|
||||||
|
menu.add(Menu.NONE, QUIT_ID, 2, "Quit Session");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContextMenuClosed(Menu menu) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||||
|
AppObject app = (AppObject) appListAdapter.getItem(info.position);
|
||||||
|
switch (item.getItemId())
|
||||||
|
{
|
||||||
|
case RESUME_ID:
|
||||||
|
// Resume is the same as start for us
|
||||||
|
doStart(app.app);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case QUIT_ID:
|
||||||
|
doQuit(app.app);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onContextItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateString(NvApp app) {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append(app.getAppName());
|
||||||
|
if (app.getIsRunning()) {
|
||||||
|
str.append(" - Running");
|
||||||
|
}
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListPlaceholder() {
|
||||||
|
appListAdapter.add(new AppObject("No apps found. Try rescanning for games in GeForce Experience.", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAppList() {
|
||||||
|
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing app list...", true);
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
NvHTTP httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final List<NvApp> appList = httpConn.getAppList();
|
||||||
|
|
||||||
|
AppView.this.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
appListAdapter.clear();
|
||||||
|
if (appList.isEmpty()) {
|
||||||
|
addListPlaceholder();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (NvApp app : appList) {
|
||||||
|
appListAdapter.add(new AppObject(generateString(app), app));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Success case
|
||||||
|
return;
|
||||||
|
} catch (GfeHttpResponseException e) {
|
||||||
|
} catch (IOException e) {
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
} finally {
|
||||||
|
spinner.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog.displayDialog(AppView.this, "Error", "Failed to get app list", true);
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doStart(NvApp app) {
|
||||||
|
Intent intent = new Intent(this, Game.class);
|
||||||
|
intent.putExtra(Game.EXTRA_HOST, ipAddress.getHostAddress());
|
||||||
|
intent.putExtra(Game.EXTRA_APP, app.getAppName());
|
||||||
|
intent.putExtra(Game.EXTRA_UNIQUEID, uniqueId);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doQuit(final NvApp app) {
|
||||||
|
Toast.makeText(AppView.this, "Quitting "+app.getAppName()+"...", Toast.LENGTH_SHORT).show();
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
NvHTTP httpConn;
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this));
|
||||||
|
if (httpConn.quitApp()) {
|
||||||
|
message = "Successfully quit "+app.getAppName();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message = "Failed to quit "+app.getAppName();
|
||||||
|
}
|
||||||
|
updateAppList();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
message = "Failed to resolve host";
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
||||||
|
+ "Try rebooting your machine or reinstalling GFE.";
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String toastMessage = message;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppObject {
|
||||||
|
public String text;
|
||||||
|
public NvApp app;
|
||||||
|
|
||||||
|
public AppObject(String text, NvApp app) {
|
||||||
|
this.text = text;
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,397 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
|
||||||
import com.limelight.nvstream.NvConnection;
|
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
|
||||||
import com.limelight.nvstream.http.PairingManager;
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
public class Connection extends Activity {
|
|
||||||
private Button statusButton, pairButton, quitButton;
|
|
||||||
private TextView hostText;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
|
||||||
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
|
||||||
private SeekBar bitrateSlider;
|
|
||||||
private TextView bitrateLabel;
|
|
||||||
|
|
||||||
private static final String DEFAULT_HOST = "";
|
|
||||||
public static final String HOST_KEY = "hostText";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
|
|
||||||
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
|
||||||
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
|
||||||
editor.apply();
|
|
||||||
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_connection);
|
|
||||||
|
|
||||||
// Hide the keyboard by default
|
|
||||||
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
|
||||||
|
|
||||||
this.statusButton = (Button) findViewById(R.id.statusButton);
|
|
||||||
this.pairButton = (Button) findViewById(R.id.pairButton);
|
|
||||||
this.quitButton = (Button) findViewById(R.id.quitButton);
|
|
||||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
|
||||||
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
|
|
||||||
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
|
|
||||||
this.rbutton1080p30 = (RadioButton) findViewById(R.id.config1080p30Selected);
|
|
||||||
this.rbutton1080p60 = (RadioButton) findViewById(R.id.config1080p60Selected);
|
|
||||||
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
|
||||||
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
|
||||||
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);
|
|
||||||
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
bitrateSlider.setMax(Game.BITRATE_CEILING);
|
|
||||||
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
|
||||||
updateBitrateLabel();
|
|
||||||
|
|
||||||
rbutton720p30.setChecked(false);
|
|
||||||
rbutton720p60.setChecked(false);
|
|
||||||
rbutton1080p30.setChecked(false);
|
|
||||||
rbutton1080p60.setChecked(false);
|
|
||||||
if (res720p) {
|
|
||||||
if (fps30) {
|
|
||||||
rbutton720p30.setChecked(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rbutton720p60.setChecked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (fps30) {
|
|
||||||
rbutton1080p30.setChecked(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rbutton1080p60.setChecked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
|
||||||
case Game.FORCE_SOFTWARE_DECODER:
|
|
||||||
forceSoftDec.setChecked(true);
|
|
||||||
autoDec.setChecked(false);
|
|
||||||
forceHardDec.setChecked(false);
|
|
||||||
break;
|
|
||||||
case Game.AUTOSELECT_DECODER:
|
|
||||||
forceSoftDec.setChecked(false);
|
|
||||||
autoDec.setChecked(true);
|
|
||||||
forceHardDec.setChecked(false);
|
|
||||||
break;
|
|
||||||
case Game.FORCE_HARDWARE_DECODER:
|
|
||||||
forceSoftDec.setChecked(false);
|
|
||||||
autoDec.setChecked(false);
|
|
||||||
forceHardDec.setChecked(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
|
||||||
boolean isChecked) {
|
|
||||||
if (!isChecked) {
|
|
||||||
// Ignore non-checked buttons
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonView == rbutton720p30) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
|
||||||
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) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
|
||||||
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) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
|
||||||
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) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
|
||||||
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) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == forceHardDec) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_HARDWARE_DECODER).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == autoDec) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.AUTOSELECT_DECODER).commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
rbutton720p30.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton720p60.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton1080p30.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton1080p60.setOnCheckedChangeListener(occl);
|
|
||||||
forceSoftDec.setOnCheckedChangeListener(occl);
|
|
||||||
forceHardDec.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() {
|
|
||||||
@Override
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.putExtra("host", Connection.this.hostText.getText().toString());
|
|
||||||
Connection.this.startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.quitButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
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, "Trying to quit Steam...", Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String macAddress;
|
|
||||||
try {
|
|
||||||
macAddress = NvConnection.getMacAddressString();
|
|
||||||
} catch (SocketException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (macAddress == null) {
|
|
||||||
LimeLog.severe("Couldn't find a MAC address");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvHTTP httpConn;
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
|
|
||||||
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider(Connection.this));
|
|
||||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
|
||||||
if (httpConn.getCurrentGame() != 0) {
|
|
||||||
if (httpConn.quitApp()) {
|
|
||||||
message = "Successfully closed Steam";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Failed to close Steam";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Steam is not running";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Device not paired with computer";
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
message = "Failed to resolve host";
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
|
||||||
+ "Try rebooting your machine or reinstalling GFE.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(Connection.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pairButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
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_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String macAddress;
|
|
||||||
try {
|
|
||||||
macAddress = NvConnection.getMacAddressString();
|
|
||||||
} catch (SocketException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (macAddress == null) {
|
|
||||||
LimeLog.severe("Couldn't find a MAC address");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvHTTP httpConn;
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
|
|
||||||
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider(Connection.this));
|
|
||||||
if (httpConn.getPairState() == PairingManager.PairState.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 {
|
|
||||||
// Should be no other values
|
|
||||||
message = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
message = "Failed to resolve host";
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
|
||||||
+ "Try rebooting your machine or reinstalling GFE.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(Connection.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBitrateLabel() {
|
|
||||||
bitrateLabel.setText("Max Bitrate: "+bitrateSlider.getProgress()+" Mbps");
|
|
||||||
}
|
|
||||||
}
|
|
@ -71,6 +71,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
|
|
||||||
private int drFlags = 0;
|
private int drFlags = 0;
|
||||||
|
|
||||||
|
public static final String EXTRA_HOST = "Host";
|
||||||
|
public static final String EXTRA_APP = "App";
|
||||||
|
public static final String EXTRA_UNIQUEID = "UniqueId";
|
||||||
|
|
||||||
public static final String PREFS_FILE_NAME = "gameprefs";
|
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||||
|
|
||||||
public static final String WIDTH_PREF_STRING = "ResH";
|
public static final String WIDTH_PREF_STRING = "ResH";
|
||||||
@ -172,7 +176,9 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
wifiLock.setReferenceCounted(false);
|
wifiLock.setReferenceCounted(false);
|
||||||
wifiLock.acquire();
|
wifiLock.acquire();
|
||||||
|
|
||||||
String host = Game.this.getIntent().getStringExtra("host");
|
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
||||||
|
String app = Game.this.getIntent().getStringExtra(EXTRA_APP);
|
||||||
|
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||||
InetAddress addr;
|
InetAddress addr;
|
||||||
boolean enableLargePackets;
|
boolean enableLargePackets;
|
||||||
try {
|
try {
|
||||||
@ -191,8 +197,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
LimeLog.info("Using large packets? "+enableLargePackets);
|
LimeLog.info("Using large packets? "+enableLargePackets);
|
||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
conn = new NvConnection(host, Game.this,
|
conn = new NvConnection(host, uniqueId, Game.this,
|
||||||
new StreamConfiguration("Steam", width, height, refreshRate, bitrate * 1000,
|
new StreamConfiguration(app, width, height, refreshRate, bitrate * 1000,
|
||||||
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
|
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
|
||||||
keybTranslator = new KeyboardTranslator(conn);
|
keybTranslator = new KeyboardTranslator(conn);
|
||||||
controllerHandler = new ControllerHandler(conn);
|
controllerHandler = new ControllerHandler(conn);
|
||||||
|
537
src/com/limelight/PcView.java
Normal file
537
src/com/limelight/PcView.java
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.computers.ComputerManagerListener;
|
||||||
|
import com.limelight.computers.ComputerManagerService;
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.nvstream.http.PairingManager;
|
||||||
|
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||||
|
import com.limelight.nvstream.wol.WakeOnLanSender;
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
|
|
||||||
|
public class PcView extends Activity {
|
||||||
|
private Button settingsButton;
|
||||||
|
private ListView pcList;
|
||||||
|
private ArrayAdapter<ComputerObject> pcListAdapter;
|
||||||
|
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||||
|
private boolean freezeUpdates, runningPolling;
|
||||||
|
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||||
|
final ComputerManagerService.ComputerManagerBinder localBinder =
|
||||||
|
((ComputerManagerService.ComputerManagerBinder)binder);
|
||||||
|
|
||||||
|
// Wait in a separate thread to avoid stalling the UI
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Wait for the binder to be ready
|
||||||
|
localBinder.waitForReady();
|
||||||
|
|
||||||
|
// Now make the binder visible
|
||||||
|
managerBinder = localBinder;
|
||||||
|
|
||||||
|
// Start updates
|
||||||
|
startComputerUpdates();
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
managerBinder = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static int APP_LIST_ID = 1;
|
||||||
|
private final static int PAIR_ID = 2;
|
||||||
|
private final static int UNPAIR_ID = 3;
|
||||||
|
private final static int WOL_ID = 4;
|
||||||
|
private final static int DELETE_ID = 5;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_pc_view);
|
||||||
|
|
||||||
|
// Bind to the computer manager service
|
||||||
|
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||||
|
Service.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
|
// Setup the list view
|
||||||
|
settingsButton = (Button)findViewById(R.id.settingsButton);
|
||||||
|
pcList = (ListView)findViewById(R.id.pcListView);
|
||||||
|
pcListAdapter = new ArrayAdapter<ComputerObject>(this, R.layout.simplerow);
|
||||||
|
pcListAdapter.setNotifyOnChange(false);
|
||||||
|
pcList.setAdapter(pcListAdapter);
|
||||||
|
pcList.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||||
|
long id) {
|
||||||
|
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(pos);
|
||||||
|
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
// Open the context menu if a PC is offline
|
||||||
|
openContextMenu(arg1);
|
||||||
|
}
|
||||||
|
else if (computer.details.pairState != PairState.PAIRED) {
|
||||||
|
// Pair an unpaired machine by default
|
||||||
|
doPair(computer.details);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doAppList(computer.details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerForContextMenu(pcList);
|
||||||
|
settingsButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
startActivity(new Intent(PcView.this, StreamSettings.class));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addListPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startComputerUpdates() {
|
||||||
|
if (managerBinder != null) {
|
||||||
|
if (runningPolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
freezeUpdates = false;
|
||||||
|
managerBinder.startPolling(new ComputerManagerListener() {
|
||||||
|
@Override
|
||||||
|
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||||
|
if (!freezeUpdates) {
|
||||||
|
PcView.this.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateListView(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
runningPolling = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopComputerUpdates() {
|
||||||
|
freezeUpdates = true;
|
||||||
|
managerBinder.stopPolling();
|
||||||
|
runningPolling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
if (managerBinder != null) {
|
||||||
|
unbindService(serviceConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
startComputerUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
stopComputerUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||||
|
stopComputerUpdates();
|
||||||
|
|
||||||
|
// Call superclass
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
|
||||||
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||||
|
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(info.position);
|
||||||
|
if (computer == null || computer.details == null) {
|
||||||
|
startComputerUpdates();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inflate the context menu
|
||||||
|
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
menu.add(Menu.NONE, WOL_ID, 1, "Send Wake-On-LAN request");
|
||||||
|
menu.add(Menu.NONE, DELETE_ID, 2, "Delete PC");
|
||||||
|
}
|
||||||
|
else if (computer.details.pairState != PairState.PAIRED) {
|
||||||
|
menu.add(Menu.NONE, PAIR_ID, 1, "Pair with PC");
|
||||||
|
if (computer.details.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||||
|
menu.add(Menu.NONE, DELETE_ID, 2, "Delete PC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
menu.add(Menu.NONE, APP_LIST_ID, 1, "View Game List");
|
||||||
|
menu.add(Menu.NONE, UNPAIR_ID, 2, "Unpair");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContextMenuClosed(Menu menu) {
|
||||||
|
startComputerUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPair(final ComputerDetails computer) {
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(PcView.this, "Pairing...", Toast.LENGTH_SHORT).show();
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
NvHTTP httpConn;
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
InetAddress addr = null;
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||||
|
addr = computer.localIp;
|
||||||
|
}
|
||||||
|
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||||
|
addr = computer.remoteIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpConn = new NvHTTP(addr,
|
||||||
|
managerBinder.getUniqueId(),
|
||||||
|
PlatformBinding.getDeviceName(),
|
||||||
|
PlatformBinding.getCryptoProvider(PcView.this));
|
||||||
|
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||||
|
message = "Already paired";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final String pinStr = PairingManager.generatePinString();
|
||||||
|
|
||||||
|
// Spin the dialog off in a thread because it blocks
|
||||||
|
Dialog.displayDialog(PcView.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 {
|
||||||
|
// Should be no other values
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
message = "Failed to resolve host";
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
||||||
|
+ "Try rebooting your machine or reinstalling GFE.";
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
|
final String toastMessage = message;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doWakeOnLan(final ComputerDetails computer) {
|
||||||
|
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
Toast.makeText(PcView.this, "Computer is online", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(PcView.this, "Waking PC...", Toast.LENGTH_SHORT).show();
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
WakeOnLanSender.sendWolPacket(computer);
|
||||||
|
message = "It may take a few seconds for your PC to wake up. " +
|
||||||
|
"If it doesn't, make sure it's configured properly for Wake-On-LAN.";
|
||||||
|
} catch (IOException e) {
|
||||||
|
message = "Failed to send wake-on-lan packets";
|
||||||
|
}
|
||||||
|
|
||||||
|
final String toastMessage = message;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUnpair(final ComputerDetails computer) {
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(PcView.this, "Unpairing...", Toast.LENGTH_SHORT).show();
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
NvHTTP httpConn;
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
InetAddress addr = null;
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||||
|
addr = computer.localIp;
|
||||||
|
}
|
||||||
|
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||||
|
addr = computer.remoteIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpConn = new NvHTTP(addr,
|
||||||
|
managerBinder.getUniqueId(),
|
||||||
|
PlatformBinding.getDeviceName(),
|
||||||
|
PlatformBinding.getCryptoProvider(PcView.this));
|
||||||
|
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||||
|
httpConn.unpair();
|
||||||
|
if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) {
|
||||||
|
message = "Unpaired successfully";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message = "Failed to unpair";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message = "Device was not paired";
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
message = "Failed to resolve host";
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
||||||
|
+ "Try rebooting your machine or reinstalling GFE.";
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String toastMessage = message;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doAppList(ComputerDetails computer) {
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
|
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent i = new Intent(this, AppView.class);
|
||||||
|
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||||
|
i.putExtra(AppView.UNIQUEID_EXTRA, managerBinder.getUniqueId());
|
||||||
|
|
||||||
|
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||||
|
i.putExtra(AppView.ADDRESS_EXTRA, computer.localIp.getAddress());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i.putExtra(AppView.ADDRESS_EXTRA, computer.remoteIp.getAddress());
|
||||||
|
}
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||||
|
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(info.position);
|
||||||
|
switch (item.getItemId())
|
||||||
|
{
|
||||||
|
case PAIR_ID:
|
||||||
|
doPair(computer.details);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case UNPAIR_ID:
|
||||||
|
doUnpair(computer.details);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case WOL_ID:
|
||||||
|
doWakeOnLan(computer.details);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case DELETE_ID:
|
||||||
|
if (managerBinder != null) {
|
||||||
|
managerBinder.removeComputer(computer.details.name);
|
||||||
|
}
|
||||||
|
removeListView(computer.details);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case APP_LIST_ID:
|
||||||
|
doAppList(computer.details);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onContextItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateString(ComputerDetails details) {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append(details.name).append(" - ");
|
||||||
|
if (details.state == ComputerDetails.State.ONLINE) {
|
||||||
|
str.append("Online ");
|
||||||
|
if (details.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||||
|
str.append("(Local) - ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str.append("(Remote) - ");
|
||||||
|
}
|
||||||
|
if (details.pairState == PairState.PAIRED) {
|
||||||
|
if (details.runningGameId == 0) {
|
||||||
|
str.append("Available");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str.append("In Game");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str.append("Not Paired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str.append("Offline");
|
||||||
|
}
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListPlaceholder() {
|
||||||
|
pcListAdapter.add(new ComputerObject("No computers found yet. Make sure your computer is running GFE " +
|
||||||
|
"or add your PC manually on the settings page.", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeListView(ComputerDetails details) {
|
||||||
|
for (int i = 0; i < pcListAdapter.getCount(); i++) {
|
||||||
|
ComputerObject computer = pcListAdapter.getItem(i);
|
||||||
|
|
||||||
|
if (details.equals(computer.details)) {
|
||||||
|
pcListAdapter.remove(computer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pcListAdapter.getCount() == 0) {
|
||||||
|
// Add the placeholder if we're down to 0 computers
|
||||||
|
addListPlaceholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateListView(ComputerDetails details) {
|
||||||
|
String computerString = generateString(details);
|
||||||
|
ComputerObject existingEntry = null;
|
||||||
|
boolean placeholderPresent = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < pcListAdapter.getCount(); i++) {
|
||||||
|
ComputerObject computer = pcListAdapter.getItem(i);
|
||||||
|
|
||||||
|
// If there's a placeholder, there's nothing else
|
||||||
|
if (computer.details == null) {
|
||||||
|
placeholderPresent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (computer.text.equals(computerString)) {
|
||||||
|
// Already up to date
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the same computer
|
||||||
|
if (details.equals(computer.details)) {
|
||||||
|
existingEntry = computer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingEntry != null) {
|
||||||
|
// Replace the information in the existing entry
|
||||||
|
existingEntry.text = computerString;
|
||||||
|
existingEntry.details = details;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the placeholder is the only object, remove it
|
||||||
|
if (placeholderPresent) {
|
||||||
|
pcListAdapter.remove(pcListAdapter.getItem(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new entry
|
||||||
|
pcListAdapter.add(new ComputerObject(computerString, details));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the view that the data has changed
|
||||||
|
pcListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ComputerObject {
|
||||||
|
public String text;
|
||||||
|
public ComputerDetails details;
|
||||||
|
|
||||||
|
public ComputerObject(String text, ComputerDetails details) {
|
||||||
|
this.text = text;
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
src/com/limelight/StreamSettings.java
Normal file
123
src/com/limelight/StreamSettings.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
public class StreamSettings extends Activity {
|
||||||
|
private Button advancedSettingsButton, addComputerButton;
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_stream_settings);
|
||||||
|
|
||||||
|
this.advancedSettingsButton = (Button) findViewById(R.id.advancedSettingsButton);
|
||||||
|
this.addComputerButton = (Button) findViewById(R.id.manuallyAddPc);
|
||||||
|
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
|
||||||
|
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
|
||||||
|
this.rbutton1080p30 = (RadioButton) findViewById(R.id.config1080p30Selected);
|
||||||
|
this.rbutton1080p60 = (RadioButton) findViewById(R.id.config1080p60Selected);
|
||||||
|
|
||||||
|
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
rbutton720p30.setChecked(false);
|
||||||
|
rbutton720p60.setChecked(false);
|
||||||
|
rbutton1080p30.setChecked(false);
|
||||||
|
rbutton1080p60.setChecked(false);
|
||||||
|
if (res720p) {
|
||||||
|
if (fps30) {
|
||||||
|
rbutton720p30.setChecked(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rbutton720p60.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (fps30) {
|
||||||
|
rbutton1080p30.setChecked(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rbutton1080p60.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView,
|
||||||
|
boolean isChecked) {
|
||||||
|
if (!isChecked) {
|
||||||
|
// Ignore non-checked buttons
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonView == rbutton720p30) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_30).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton720p60) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_60).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton1080p30) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_30).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton1080p60) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_60).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rbutton720p30.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton720p60.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton1080p30.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton1080p60.setOnCheckedChangeListener(occl);
|
||||||
|
|
||||||
|
advancedSettingsButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent i = new Intent(StreamSettings.this, AdvancedSettings.class);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addComputerButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent i = new Intent(StreamSettings.this, AddComputerManually.class);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
159
src/com/limelight/computers/ComputerDatabaseManager.java
Normal file
159
src/com/limelight/computers/ComputerDatabaseManager.java
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package com.limelight.computers;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
|
||||||
|
public class ComputerDatabaseManager {
|
||||||
|
private static final String COMPUTER_DB_NAME = "computers.db";
|
||||||
|
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||||
|
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
||||||
|
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
||||||
|
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
|
||||||
|
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
||||||
|
private static final String MAC_COLUMN_NAME = "Mac";
|
||||||
|
|
||||||
|
private SQLiteDatabase computerDb;
|
||||||
|
|
||||||
|
public ComputerDatabaseManager(Context c) {
|
||||||
|
try {
|
||||||
|
// Create or open an existing DB
|
||||||
|
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||||
|
} catch (SQLiteException e) {
|
||||||
|
// Delete the DB and try again
|
||||||
|
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||||
|
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||||
|
}
|
||||||
|
computerDb.enableWriteAheadLogging();
|
||||||
|
initializeDb();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
computerDb.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeDb() {
|
||||||
|
// Create tables if they aren't already there
|
||||||
|
computerDb.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," +
|
||||||
|
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
|
||||||
|
COMPUTER_TABLE_NAME,
|
||||||
|
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
|
||||||
|
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteComputer(String name) {
|
||||||
|
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateComputer(ComputerDetails details) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||||
|
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||||
|
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
|
||||||
|
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
|
||||||
|
values.put(MAC_COLUMN_NAME, details.macAddress);
|
||||||
|
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ComputerDetails> getAllComputers() {
|
||||||
|
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||||
|
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
ComputerDetails details = new ComputerDetails();
|
||||||
|
|
||||||
|
details.name = c.getString(0);
|
||||||
|
|
||||||
|
String uuidStr = c.getString(1);
|
||||||
|
try {
|
||||||
|
details.uuid = UUID.fromString(uuidStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.macAddress = c.getString(4);
|
||||||
|
|
||||||
|
// This signifies we don't have dynamic state (like pair state)
|
||||||
|
details.state = ComputerDetails.State.UNKNOWN;
|
||||||
|
|
||||||
|
// If a field is corrupt or missing, skip the database entry
|
||||||
|
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||||
|
details.macAddress == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
computerList.add(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
return computerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComputerDetails getComputerByName(String name) {
|
||||||
|
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||||
|
ComputerDetails details = new ComputerDetails();
|
||||||
|
if (!c.moveToFirst()) {
|
||||||
|
// No matching computer
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.name = c.getString(0);
|
||||||
|
|
||||||
|
String uuidStr = c.getString(1);
|
||||||
|
try {
|
||||||
|
details.uuid = UUID.fromString(uuidStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.macAddress = c.getString(4);
|
||||||
|
|
||||||
|
// If a field is corrupt or missing, delete the database entry
|
||||||
|
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||||
|
details.macAddress == null) {
|
||||||
|
deleteComputer(details.name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
}
|
7
src/com/limelight/computers/ComputerManagerListener.java
Normal file
7
src/com/limelight/computers/ComputerManagerListener.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.limelight.computers;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
|
public interface ComputerManagerListener {
|
||||||
|
public void notifyComputerUpdated(ComputerDetails details);
|
||||||
|
}
|
279
src/com/limelight/computers/ComputerManagerService.java
Normal file
279
src/com/limelight/computers/ComputerManagerService.java
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package com.limelight.computers;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.discovery.DiscoveryService;
|
||||||
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.nvstream.mdns.MdnsComputer;
|
||||||
|
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
public class ComputerManagerService extends Service {
|
||||||
|
private static final int MAX_CONCURRENT_REQUESTS = 4;
|
||||||
|
private static final int POLLING_PERIOD_MS = 5000;
|
||||||
|
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||||
|
|
||||||
|
private ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||||
|
|
||||||
|
private ComputerDatabaseManager dbManager;
|
||||||
|
private IdentityManager idManager;
|
||||||
|
private ThreadPoolExecutor pollingPool;
|
||||||
|
private Timer pollingTimer;
|
||||||
|
private ComputerManagerListener listener = null;
|
||||||
|
|
||||||
|
private DiscoveryService.DiscoveryBinder discoveryBinder;
|
||||||
|
private ServiceConnection discoveryServiceConnection = new ServiceConnection() {
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||||
|
synchronized (discoveryServiceConnection) {
|
||||||
|
discoveryBinder = ((DiscoveryService.DiscoveryBinder)binder);
|
||||||
|
|
||||||
|
// Set us as the event listener
|
||||||
|
discoveryBinder.setListener(createDiscoveryListener());
|
||||||
|
|
||||||
|
// Signal a possible waiter that we're all setup
|
||||||
|
discoveryServiceConnection.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
discoveryBinder = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public class ComputerManagerBinder extends Binder {
|
||||||
|
public void startPolling(ComputerManagerListener listener) {
|
||||||
|
// Set the listener
|
||||||
|
ComputerManagerService.this.listener = listener;
|
||||||
|
|
||||||
|
// Start mDNS autodiscovery too
|
||||||
|
discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS);
|
||||||
|
|
||||||
|
// Start polling known machines
|
||||||
|
pollingTimer = new Timer();
|
||||||
|
pollingTimer.schedule(getTimerTask(), 0, POLLING_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitForReady() {
|
||||||
|
synchronized (discoveryServiceConnection) {
|
||||||
|
try {
|
||||||
|
while (discoveryBinder == null) {
|
||||||
|
// Wait for the bind notification
|
||||||
|
discoveryServiceConnection.wait(1000);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addComputerBlocking(InetAddress addr) {
|
||||||
|
return ComputerManagerService.this.addComputerBlocking(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addComputer(InetAddress addr) {
|
||||||
|
ComputerManagerService.this.addComputer(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeComputer(String name) {
|
||||||
|
ComputerManagerService.this.removeComputer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopPolling() {
|
||||||
|
// Just call the unbind handler to cleanup
|
||||||
|
ComputerManagerService.this.onUnbind(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUniqueId() {
|
||||||
|
return idManager.getUniqueId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
// Stop mDNS autodiscovery
|
||||||
|
discoveryBinder.stopDiscovery();
|
||||||
|
|
||||||
|
// Stop polling
|
||||||
|
if (pollingTimer != null) {
|
||||||
|
pollingTimer.cancel();
|
||||||
|
pollingTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the listener
|
||||||
|
listener = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MdnsDiscoveryListener createDiscoveryListener() {
|
||||||
|
return new MdnsDiscoveryListener() {
|
||||||
|
@Override
|
||||||
|
public void notifyComputerAdded(MdnsComputer computer) {
|
||||||
|
LimeLog.severe("Added computer: "+computer.getName());
|
||||||
|
// Kick off a serverinfo poll on this machine
|
||||||
|
addComputer(computer.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDiscoveryFailure(Exception e) {
|
||||||
|
LimeLog.severe("mDNS discovery failed");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addComputer(InetAddress addr) {
|
||||||
|
// Setup a placeholder
|
||||||
|
ComputerDetails fakeDetails = new ComputerDetails();
|
||||||
|
fakeDetails.localIp = addr;
|
||||||
|
fakeDetails.remoteIp = addr;
|
||||||
|
|
||||||
|
// Put it in the thread pool to process later
|
||||||
|
pollingPool.execute(getPollingRunnable(fakeDetails));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addComputerBlocking(InetAddress addr) {
|
||||||
|
// Setup a placeholder
|
||||||
|
ComputerDetails fakeDetails = new ComputerDetails();
|
||||||
|
fakeDetails.localIp = addr;
|
||||||
|
fakeDetails.remoteIp = addr;
|
||||||
|
|
||||||
|
// Block while we try to fill the details
|
||||||
|
getPollingRunnable(fakeDetails).run();
|
||||||
|
|
||||||
|
// If the machine is reachable, it was successful
|
||||||
|
return fakeDetails.state == ComputerDetails.State.ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeComputer(String name) {
|
||||||
|
// Remove it from the database
|
||||||
|
dbManager.deleteComputer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimerTask getTimerTask() {
|
||||||
|
return new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<ComputerDetails> computerList = dbManager.getAllComputers();
|
||||||
|
for (ComputerDetails computer : computerList) {
|
||||||
|
pollingPool.execute(getPollingRunnable(computer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable getPollingRunnable(final ComputerDetails details) {
|
||||||
|
return new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
boolean newPc = details.name == null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try the local IP first
|
||||||
|
NvHTTP http = new NvHTTP(details.localIp, idManager.getUniqueId(),
|
||||||
|
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
|
|
||||||
|
ComputerDetails localDetails = http.getComputerDetails();
|
||||||
|
|
||||||
|
// If we got here, it's reachable
|
||||||
|
details.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
|
details.update(localDetails);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// This isn't horrible yet; we'll try remote
|
||||||
|
try {
|
||||||
|
NvHTTP http = new NvHTTP(details.remoteIp, idManager.getUniqueId(),
|
||||||
|
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
|
|
||||||
|
ComputerDetails remoteDetails = http.getComputerDetails();
|
||||||
|
|
||||||
|
// If we got here, it's reachable
|
||||||
|
details.reachability = ComputerDetails.Reachability.REMOTE;
|
||||||
|
details.update(remoteDetails);
|
||||||
|
} catch (Exception e1) {
|
||||||
|
// No good, it's offline
|
||||||
|
details.state = ComputerDetails.State.OFFLINE;
|
||||||
|
details.reachability = ComputerDetails.Reachability.OFFLINE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's online, update our persistent state
|
||||||
|
if (details.state == ComputerDetails.State.ONLINE) {
|
||||||
|
if (!newPc) {
|
||||||
|
// Check if it's in the database because it could have been
|
||||||
|
// removed after this was issued
|
||||||
|
if (dbManager.getComputerByName(details.name) == null) {
|
||||||
|
// It's gone
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbManager.updateComputer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update anyone listening
|
||||||
|
if (listener != null) {
|
||||||
|
listener.notifyComputerUpdated(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
// Bind to the discovery service
|
||||||
|
bindService(new Intent(this, DiscoveryService.class),
|
||||||
|
discoveryServiceConnection, Service.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
|
// Create the thread pool for updating computer state
|
||||||
|
pollingPool = new ThreadPoolExecutor(1, MAX_CONCURRENT_REQUESTS, Long.MAX_VALUE, TimeUnit.DAYS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.DiscardPolicy());
|
||||||
|
|
||||||
|
// Lookup or generate this device's UID
|
||||||
|
idManager = new IdentityManager(this);
|
||||||
|
|
||||||
|
// Initialize the DB
|
||||||
|
dbManager = new ComputerDatabaseManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (discoveryBinder != null) {
|
||||||
|
// Unbind from the discovery service
|
||||||
|
unbindService(discoveryServiceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the thread pool
|
||||||
|
pollingPool.shutdownNow();
|
||||||
|
try {
|
||||||
|
pollingPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
|
||||||
|
// Close the DB
|
||||||
|
dbManager.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
}
|
85
src/com/limelight/computers/IdentityManager.java
Normal file
85
src/com/limelight/computers/IdentityManager.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package com.limelight.computers;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class IdentityManager {
|
||||||
|
private static final String UNIQUE_ID_FILE_NAME = "uniqueid";
|
||||||
|
private static final int UID_SIZE_IN_BYTES = 8;
|
||||||
|
|
||||||
|
private String uniqueId;
|
||||||
|
|
||||||
|
public IdentityManager(Context c) {
|
||||||
|
uniqueId = loadUniqueId(c);
|
||||||
|
if (uniqueId == null) {
|
||||||
|
uniqueId = generateNewUniqueId(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("UID is now: "+uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUniqueId() {
|
||||||
|
return uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String loadUniqueId(Context c) {
|
||||||
|
// 2 Hex digits per byte
|
||||||
|
char[] uid = new char[UID_SIZE_IN_BYTES * 2];
|
||||||
|
InputStreamReader reader = null;
|
||||||
|
LimeLog.info("Reading UID from disk");
|
||||||
|
try {
|
||||||
|
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
|
||||||
|
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
|
||||||
|
{
|
||||||
|
LimeLog.severe("UID file data is truncated");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(uid);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LimeLog.info("No UID file found");
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LimeLog.severe("Error while reading UID file");
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateNewUniqueId(Context c) {
|
||||||
|
// Generate a new UID hex string
|
||||||
|
LimeLog.info("Generating new UID");
|
||||||
|
String uidStr = String.format("%016x", new Random().nextLong());
|
||||||
|
|
||||||
|
OutputStreamWriter writer = null;
|
||||||
|
try {
|
||||||
|
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
|
||||||
|
writer.write(uidStr);
|
||||||
|
LimeLog.info("UID written to disk");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LimeLog.severe("Error while writing UID file");
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (writer != null) {
|
||||||
|
try {
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can return a UID even if I/O fails
|
||||||
|
return uidStr;
|
||||||
|
}
|
||||||
|
}
|
@ -77,6 +77,7 @@ public class DiscoveryService extends Service {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
discoveryAgent = null;
|
discoveryAgent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user