Compare commits

...

93 Commits

Author SHA1 Message Date
Cameron Gutman 3d177e97e4 Add support for displaying the rendered frame times 2016-11-17 23:34:56 -08:00
Cameron Gutman 6c3aaedc83 Version 4.8.1 2016-11-13 20:30:26 -08:00
Cameron Gutman bf84ebef6d Fix help launch crash with MxPlayer acting as default browser 2016-11-13 20:29:34 -08:00
Cameron Gutman 8991b29329 Run at maximum decoder operating rate on M 2016-11-13 20:26:58 -08:00
Cameron Gutman fa84575be5 Increment version code 2016-11-11 14:04:28 -08:00
Cameron Gutman 0432d5725b Properly handle Xbox One S controllers with updated firmware 2016-11-11 14:03:20 -08:00
Cameron Gutman 8e7b144339 Increment version code 2016-11-10 19:58:14 -08:00
Cameron Gutman fc629db653 Improve shortcut handling when the list is full 2016-11-10 19:45:17 -08:00
Cameron Gutman d5863e1bef Only try twice before initially reporting a PC as offline 2016-11-09 21:08:27 -08:00
Cameron Gutman c2c3a6b37c Increment version code 2016-11-07 19:00:39 -08:00
Cameron Gutman e701699dea Improve launcher shortcut backstack and stop leaking a ServiceConnection 2016-11-07 18:58:59 -08:00
Cameron Gutman 17179bd027 Update version to 4.8 2016-11-06 01:08:56 -07:00
Cameron Gutman b2f210700d Update common with mouse move trick 2016-11-06 01:06:07 -07:00
Cameron Gutman f0e85c4c53 Fix some additional launcher shortcut issues 2016-11-06 00:43:18 -07:00
Cameron Gutman 92f8425ace Better handle offline PCs in launcher shortcuts 2016-11-06 00:24:04 -07:00
Cameron Gutman 6ad001e8be Update help viewer for Amazon devices 2016-11-06 00:01:37 -07:00
Cameron Gutman b6e4d5528b Fix and enable launcher shortcuts on Android 7.1 2016-11-05 21:29:59 -07:00
Cameron Gutman 0f0b83badc Properly invalidate pairing state after pairing 2016-11-05 20:20:09 -07:00
Cameron Gutman 453fbb5f58 Use scancode mapping for DS4 for consistent mappings across devices 2016-11-05 20:08:42 -07:00
Cameron Gutman e7dc3a4c11 Timeout polling results after 30 seconds 2016-11-05 19:56:54 -07:00
Cameron Gutman d68b2382cf Integrate help buttons into dialogs and the PcView 2016-11-05 19:51:43 -07:00
Cameron Gutman 1b5330323c Fix root mouse capture on Nougat 2016-11-04 00:14:05 -07:00
Cameron Gutman 8aba4888e1 Fix being unable to press the same key down on different gamepads at the same time 2016-10-30 15:26:52 -07:00
Cameron Gutman 1c3b9a3859 Fix race condition in USB driver initialization and cleanup 2016-10-27 18:09:33 -07:00
Cameron Gutman e8f04f5a3b Cleanup ShortcutHelper 2016-10-26 12:28:29 -07:00
Cameron Gutman 56b814e877 Bump versioncode 2016-10-21 16:22:07 -07:00
Cameron Gutman 628ccd39d6 Fix default context getting picked up falsely during gamepad removal 2016-10-21 16:19:05 -07:00
Cameron Gutman 59db3f9b62 Fix Xbox button behavior quirks on Xbox One S controller 2016-10-21 15:57:00 -07:00
Cameron Gutman 416f922b56 Fix triggers stuck at 50% after controller reconnect 2016-10-21 14:54:21 -07:00
Cameron Gutman b52a86e6cc Fix app grid isRunning icon not displaying 2016-10-21 14:04:31 -07:00
Cameron Gutman e523b5069e Version 4.7.3 2016-10-21 13:48:03 -07:00
Cameron Gutman e8ae8d9807 Manually set pairing state after pairing 2016-10-21 13:47:03 -07:00
Cameron Gutman 64e56a861d Ignore case when sorting apps and PCs 2016-10-21 13:44:59 -07:00
Cameron Gutman c1bcd09c9b Disable launcher shortcuts pending further work 2016-10-21 12:52:37 -07:00
Cameron Gutman 574258804f Update to AS 2.2.2 and SDK 25 2016-10-21 12:50:04 -07:00
Cameron Gutman 21ea3d8a2b Fix 3rd party Xbox controller d-pads 2016-10-21 12:38:55 -07:00
Cameron Gutman 6de4288a85 Fix running app state on GFE 3.1 2016-10-21 12:28:15 -07:00
Cameron Gutman a107b5e652 Add launcher shortcuts and fix duplicate pairing error 2016-10-20 13:09:24 -07:00
Cameron Gutman b02db2c182 Fix JNI build warnings with modern NDKs 2016-10-19 20:47:23 -07:00
Cameron Gutman f8a04cda7a Version 4.7.2 2016-10-05 18:53:36 -07:00
Cameron Gutman 226e8edefc Update common jar to 55f0114 2016-10-05 18:49:57 -07:00
Cameron Gutman 9b90b30a1f Update build tools and version code 2016-09-28 22:31:55 -07:00
Cameron Gutman 2ed245b25a Add some extra text for GFE 3.0, since GameStream isn't on by default 2016-09-28 22:30:05 -07:00
Cameron Gutman 4b769839d0 Update common jar to 3c56730 2016-09-24 20:57:19 -07:00
Cameron Gutman 239dd1d5a1 Only display box art progress bar if a network load is required 2016-09-20 12:11:06 -07:00
Cameron Gutman 37509cce9b Update common jar to a76df84 2016-09-20 11:29:13 -07:00
Cameron Gutman 227c71549b Migrate project and NDK build to Android Studio 2.2 2016-09-20 11:26:56 -07:00
Cameron Gutman a10d8334f3 Update Gradle 2016-08-26 22:02:51 -07:00
Cameron Gutman 0fc61e52dd Fix Lint error in translation 2016-08-13 19:24:49 -07:00
Cameron Gutman 5e44c33bb6 Version 4.7 2016-08-13 19:22:29 -07:00
Cameron Gutman df3655e958 Add support for Xbox One S controller over USB 2016-08-13 19:21:49 -07:00
Cameron Gutman fe43e13145 Set larger dimensions for vector drawables so the generated PNGs are larger 2016-08-13 19:18:42 -07:00
Cameron Gutman acd3aad8d9 Add support for mouse emulation with a gamepad 2016-08-13 18:52:39 -07:00
Cameron Gutman 811b4b4f22 Better center overlays on PC view 2016-08-13 17:48:30 -07:00
Cameron Gutman 7db3b9f401 Use Material icons 2016-08-13 16:45:42 -07:00
Cameron Gutman a5a099cf43 Update common jar with 4K fix 2016-08-13 14:51:07 -07:00
Cameron Gutman ba605643bb Switch to indeterminate progress bars 2016-08-13 14:42:03 -07:00
Cameron Gutman 96e98c1abb Update translations 2016-08-13 12:32:49 -07:00
Cameron Gutman 5de6f6ae2b Fix build with Dutch translation 2016-08-13 12:29:08 -07:00
Cameron Gutman 0685722773 Merge branch 'master' of github.com:moonlight-stream/moonlight-android 2016-08-13 12:20:54 -07:00
Cameron Gutman 29df3b2859 Merge branch 'master' of https://github.com/halluci/moonlight-android 2016-08-13 12:14:37 -07:00
DragonSpirit fc6f859ced Russian Translation Added (#146) 2016-08-13 12:10:02 -07:00
Subject 6b21a5416f Dutch Translation Added (#118)
* Halfway through string translation

* Fixed up translation -- Ready for pull request
2016-08-13 12:09:39 -07:00
jeid64 74e7c8bbf1 Add basic building instructions to README (#227)
* Add basic building instructions to README

Added instructions to build APK for developers. Included getting submodules and installing NDK.

* Spelling fixes. Added ndk.dir
2016-08-13 12:07:20 -07:00
Cameron Gutman 757075b16a Add support for Xbox One S controller connected via Bluetooth 2016-08-13 12:01:52 -07:00
Cameron Gutman e8903c4d48 Update build tools to 24.0.1 2016-08-13 11:45:38 -07:00
halluci 98262d16ee Japanese translation added:updated 2016-07-17 22:01:49 +09:00
halluci 339506cf10 Japanese translation added 2016-07-17 17:22:30 +09:00
Cameron Gutman 63bd5df09b Prefer Evdev over N native capture since Evdev can capture over the system UI 2016-07-13 23:12:29 -07:00
Cameron Gutman 32af2d0831 Increment version code 2016-06-26 14:05:38 -07:00
Cameron Gutman 242b03d4b5 Add gradle.properties for Dex In-Process 2016-06-20 21:33:39 -07:00
Cameron Gutman 87a62666ac Prefer Shield capture provider over Android N 2016-06-20 20:43:59 -07:00
Cameron Gutman 2dcf5486da Revert "Display the running app first on the app grid"
This reverts commit 36f8cc02cb.
2016-06-20 20:30:47 -07:00
Cameron Gutman 60d3d8b3ae Version to 4.6 2016-06-18 15:17:15 -07:00
Cameron Gutman e9141d65fe Improve reliability of missing root detection 2016-06-18 14:54:53 -07:00
Cameron Gutman aae591daec Improve multi-window experience on N 2016-06-18 14:52:20 -07:00
Cameron Gutman a5ca8a7472 Add a hack to avoid crashing when the app window divider is dragged off of the screen on N multi-window 2016-06-18 14:40:42 -07:00
Cameron Gutman 36f8cc02cb Display the running app first on the app grid 2016-06-18 13:38:37 -07:00
Cameron Gutman 55b9645651 Fix minor Lint issues 2016-06-18 12:38:43 -07:00
Cameron Gutman d30ecbed5b Update gradle 2016-06-18 11:40:55 -07:00
Cameron Gutman 0bbd27f04c Update common jar 2016-06-18 11:37:00 -07:00
Cameron Gutman 3c53fb7403 Update target SDK to 24 2016-06-18 11:19:04 -07:00
Cameron Gutman 7a81950819 Enable sustained performance mode on N+ when streaming 2016-06-18 11:17:34 -07:00
Cameron Gutman 74f212c702 Add Android N mouse capture support 2016-06-18 11:15:53 -07:00
Cameron Gutman 36be943854 Add support for more Xbox controller models 2016-06-13 22:28:48 -05:00
Cameron Gutman 26a4fc75a5 Add handling for the ADT-1 controller 2016-06-13 21:28:54 -05:00
Cameron Gutman a5ec5fc265 Select the optimal display mode before streaming 2016-06-13 21:23:00 -05:00
Cameron Gutman 541ac44be4 Add an unified input capture interface 2016-06-13 20:33:43 -05:00
Cameron Gutman 117b555fcd Fix wiki link 2016-05-30 12:22:54 -05:00
Cameron Gutman a10cd04441 Clarify wording in H.265 settings 2016-05-29 16:39:20 -05:00
Cameron Gutman 53dccbde2a Repeat key down events are needed for proper key repeating 2016-05-29 15:52:18 -05:00
Cameron Gutman 56625dfe4b Bump version to 4.5.10 2016-05-21 18:28:02 -05:00
Cameron Gutman 2eab5a3b7b Update the ENet submodule to include the MTU fix for LTE streaming 2016-05-21 18:09:40 -05:00
82 changed files with 2157 additions and 545 deletions
+2 -1
View File
@@ -32,4 +32,5 @@ Thumbs.db
build/
# Compiled JNI libraries folder
**/jniLibs
**/jniLibs
app/.externalNativeBuild/
+7 -1
View File
@@ -8,7 +8,7 @@ whether in your own home or over the internet.
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for more detailed information or a troubleshooting guide.
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
##Features
@@ -46,6 +46,12 @@ This project is being actively developed at [XDA Developers](http://forum.xda-de
2. Write code
3. Send Pull Requests
##Building
* Install Android Studio and the Android NDK
* Run git submodule update --init --recursive from within moonlight-android/
* In moonlight-android/, create a file called local.properties. Add an ndk.dir= property to the local.properties file and set it equal to your NDK directory.
* Build the APK using Android Studio
##Authors
* [Cameron Gutman](https://github.com/cgutman)
+53 -20
View File
@@ -12,10 +12,7 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleNonRootDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleNonRootDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileNonRootDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateNonRootDebugAndroidTestSources</task>
<task>generateNonRootDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
@@ -25,16 +22,26 @@
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
<facet type="native-android-gradle" name="Native-Android-Gradle">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="nonRootDebug" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/nonRoot/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/nonRoot/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/jnienet/enet" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/jnienet" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/evdev_reader" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/nv_opus_dec" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
@@ -42,71 +49,97 @@
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRootDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testNonRoot/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndkBuild" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.52" level="project" />
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.52" level="project" />
+14 -30
View File
@@ -4,24 +4,30 @@ import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
targetSdkVersion 25
versionName "4.5.9"
versionCode = 100
versionName "4.8.1"
versionCode = 113
}
productFlavors {
root {
applicationId "com.limelight.root"
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
}
}
nonRoot {
applicationId "com.limelight"
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
}
}
}
@@ -43,32 +49,10 @@ android {
exclude 'META-INF/BCKEY.DSA'
}
sourceSets.main.jni.srcDirs = []
//noinspection GroovyAssignabilityCheck,GroovyAssignabilityCheck
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkDir = properties.getProperty('ndk.dir')
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine "$ndkDir\\ndk-build.cmd",
'NDK_PROJECT_PATH=build/intermediates/ndk',
'NDK_LIBS_OUT=src/main/jniLibs',
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
else {
commandLine "$ndkDir/ndk-build",
'NDK_PROJECT_PATH=build/intermediates/ndk',
'NDK_LIBS_OUT=src/main/jniLibs',
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
}
Binary file not shown.
+44 -17
View File
@@ -7,60 +7,85 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:banner="@drawable/atv_banner"
android:theme="@style/AppTheme" >
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
<!-- Samsung multi-window support -->
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<uses-library
android:name="com.sec.android.app.multiwindow"
android:required="false" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
<activity
android:name=".PcView"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<category android:name="tv.ouya.intent.category.APP" />
</intent-filter>
</activity>
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
<activity
android:name=".AppViewShortcutTrampoline"
android:noHistory="true"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.PcView" />
</activity>
<activity
android:name=".AppView"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|fontScale|uiMode" >
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.PcView" />
</activity>
<activity
android:name=".preferences.StreamSettings"
android:label="Streaming Settings" >
android:label="Streaming Settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.PcView" />
</activity>
<activity
android:name=".preferences.AddComputerManually"
android:label="Add Computer Manually" >
android:label="Add Computer Manually">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.PcView" />
</activity>
<activity
android:name=".Game"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
android:screenOrientation="sensorLandscape"
android:theme="@style/StreamTheme"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
android:theme="@style/StreamTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.AppView" />
@@ -75,6 +100,8 @@
<service
android:name=".binding.input.driver.UsbDriverService"
android:label="Usb Driver Service" />
<activity android:name=".HelpActivity"></activity>
</application>
</manifest>
</manifest>
+46 -29
View File
@@ -11,12 +11,14 @@ import com.limelight.grid.AppGridAdapter;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.http.PairingManager;
import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.AdapterFragment;
import com.limelight.ui.AdapterFragmentCallbacks;
import com.limelight.utils.CacheHelper;
import com.limelight.utils.Dialog;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -43,6 +45,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter;
private String uuidString;
private ShortcutHelper shortcutHelper;
private ComputerDetails computer;
private ComputerManagerService.ApplistPoller poller;
@@ -90,12 +93,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
return;
}
// Load the app grid with cached data (if possible).
// This must be done _before_ startComputerUpdates()
// so the initial serverinfo response can update the running
// icon.
populateAppGridWithCache();
// Start updates
startComputerUpdates();
// Load the app grid with cached data (if possible)
populateAppGridWithCache();
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -132,7 +138,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
managerBinder.startPolling(new ComputerManagerListener() {
@Override
public void notifyComputerUpdated(ComputerDetails details) {
public void notifyComputerUpdated(final ComputerDetails details) {
// Do nothing if updates are suspended
if (suspendGridUpdates) {
return;
@@ -157,6 +163,24 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
return;
}
// Close immediately if the PC is no longer paired
if (details.state == ComputerDetails.State.ONLINE && details.pairState != PairingManager.PairState.PAIRED) {
AppView.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// Disable shortcuts referencing this PC for now
shortcutHelper.disableShortcut(details.uuid.toString(),
getResources().getString(R.string.scut_not_paired));
// Display a toast to the user and quit the activity
Toast.makeText(AppView.this, getResources().getText(R.string.scut_not_paired), Toast.LENGTH_SHORT).show();
finish();
}
});
return;
}
// App list is the same or empty
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
@@ -175,6 +199,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
try {
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
updateUiWithServerinfo(details);
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
@@ -208,6 +233,8 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
shortcutHelper = new ShortcutHelper(this);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
@@ -221,11 +248,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
uuidString = getIntent().getStringExtra(UUID_EXTRA);
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
String computerName = getIntent().getStringExtra(NAME_EXTRA);
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
TextView label = (TextView) findViewById(R.id.appListText);
setTitle(labelText);
label.setText(labelText);
// Add a launcher shortcut for this PC (forced, since this is user interaction)
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
shortcutHelper.reportShortcutUsed(uuidString);
// Bind to the computer manager service
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE);
@@ -282,27 +315,14 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
stopComputerUpdates();
}
private int getRunningAppId() {
int runningAppId = -1;
for (int i = 0; i < appGridAdapter.getCount(); i++) {
AppObject app = (AppObject) appGridAdapter.getItem(i);
if (app.app.getIsRunning()) {
runningAppId = app.app.getAppId();
break;
}
}
return runningAppId;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
int runningAppId = getRunningAppId();
if (runningAppId != -1) {
if (runningAppId == selectedApp.app.getAppId()) {
if (lastRunningAppId != 0) {
if (lastRunningAppId == selectedApp.app.getAppId()) {
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
}
@@ -378,19 +398,19 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
// There can only be one or zero apps running.
if (existingApp.app.getIsRunning() &&
if (existingApp.isRunning &&
existingApp.app.getAppId() == details.runningGameId) {
// This app was running and still is, so we're done now
return;
}
else if (existingApp.app.getAppId() == details.runningGameId) {
// This app wasn't running but now is
existingApp.app.setIsRunning(true);
existingApp.isRunning = true;
updated = true;
}
else if (existingApp.app.getIsRunning()) {
else if (existingApp.isRunning) {
// This app was running but now isn't
existingApp.app.setIsRunning(false);
existingApp.isRunning = false;
updated = true;
}
else {
@@ -420,10 +440,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
if (existingApp.app.getAppId() == app.getAppId()) {
// Found the app; update its properties
if (existingApp.app.getIsRunning() != app.getIsRunning()) {
existingApp.app.setIsRunning(app.getIsRunning());
updated = true;
}
if (!existingApp.app.getAppName().equals(app.getAppName())) {
existingApp.app.setAppName(app.getAppName());
updated = true;
@@ -493,7 +509,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
AppObject app = (AppObject) appGridAdapter.getItem(pos);
// Only open the context menu if something is running, otherwise start it
if (getRunningAppId() != -1) {
if (lastRunningAppId != 0) {
openContextMenu(arg1);
} else {
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
@@ -506,6 +522,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
public class AppObject {
public final NvApp app;
public boolean isRunning;
public AppObject(NvApp app) {
if (app == null) {
@@ -0,0 +1,164 @@
package com.limelight;
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 com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
import com.limelight.utils.Dialog;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
import java.util.ArrayList;
import java.util.UUID;
public class AppViewShortcutTrampoline extends Activity {
private String uuidString;
private ComputerDetails computer;
private SpinnerDialog blockingLoadSpinner;
public final static String UUID_EXTRA = "UUID";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final 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;
// Get the computer object
computer = managerBinder.getComputer(UUID.fromString(uuidString));
// Force CMS to repoll this machine
managerBinder.invalidateStateForComputer(computer.uuid);
// Start polling
managerBinder.startPolling(new ComputerManagerListener() {
@Override
public void notifyComputerUpdated(final ComputerDetails details) {
// Don't care about other computers
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
return;
}
if (details.state != ComputerDetails.State.UNKNOWN) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Stop showing the spinner
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
blockingLoadSpinner = null;
}
if (details.state == ComputerDetails.State.ONLINE) {
// Close this activity
finish();
// Create a new activity stack for this launch
ArrayList<Intent> intentStack = new ArrayList<>();
Intent i;
// Add the PC view at the back (and clear the task)
i = new Intent(AppViewShortcutTrampoline.this, PcView.class);
i.setAction(Intent.ACTION_MAIN);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
intentStack.add(i);
// Take this intent's data and create an intent to start the app view
i = new Intent(getIntent());
i.setClass(AppViewShortcutTrampoline.this, AppView.class);
intentStack.add(i);
// If a game is running, we'll make the stream the top level activity
if (details.runningGameId != 0) {
intentStack.add(ServerHelper.createStartIntent(AppViewShortcutTrampoline.this,
new NvApp("app", details.runningGameId), details, managerBinder));
}
// Now start the activities
startActivities(intentStack.toArray(new Intent[]{}));
}
else if (details.state == ComputerDetails.State.OFFLINE) {
// Computer offline - display an error dialog
Dialog.displayDialog(AppViewShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.error_pc_offline),
true);
}
// We don't want any more callbacks from now on, so go ahead
// and unbind from the service
if (managerBinder != null) {
managerBinder.stopPolling();
unbindService(serviceConnection);
managerBinder = null;
}
}
});
}
}
});
}
}.start();
}
public void onServiceDisconnected(ComponentName className) {
managerBinder = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UiHelper.notifyNewRootView(this);
uuidString = getIntent().getStringExtra(UUID_EXTRA);
// Bind to the computer manager service
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE);
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title),
getResources().getString(R.string.applist_connect_msg), true);
}
@Override
protected void onPause() {
super.onPause();
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
blockingLoadSpinner = null;
}
Dialog.closeDialogs();
if (managerBinder != null) {
managerBinder.stopPolling();
unbindService(serviceConnection);
managerBinder = null;
}
finish();
}
}
+134 -64
View File
@@ -4,10 +4,10 @@ package com.limelight;
import com.limelight.binding.PlatformBinding;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.input.NvMouseHelper;
import com.limelight.binding.input.capture.InputCaptureManager;
import com.limelight.binding.input.capture.InputCaptureProvider;
import com.limelight.binding.input.TouchContext;
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.binding.input.evdev.EvdevHandler;
import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.video.EnhancedDecoderRenderer;
@@ -24,6 +24,7 @@ import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.GameGestures;
import com.limelight.ui.StreamView;
import com.limelight.utils.Dialog;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import android.annotation.SuppressLint;
@@ -39,6 +40,7 @@ import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -48,12 +50,10 @@ import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnGenericMotionListener;
import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -92,12 +92,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private boolean connecting = false;
private boolean connected = false;
private EvdevHandler evdevHandler;
private InputCaptureProvider inputCaptureProvider;
private int modifierFlags = 0;
private boolean grabbedInput = true;
private boolean grabComboDown = false;
private StreamView streamView;
private ShortcutHelper shortcutHelper;
private EnhancedDecoderRenderer decoderRenderer;
private WifiManager.WifiLock wifiLock;
@@ -124,11 +126,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
public static final String EXTRA_APP_ID = "AppId";
public static final String EXTRA_UNIQUEID = "UniqueId";
public static final String EXTRA_STREAMING_REMOTE = "Remote";
public static final String EXTRA_PC_UUID = "UUID";
public static final String EXTRA_PC_NAME = "PcName";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
shortcutHelper = new ShortcutHelper(this);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
@@ -194,12 +200,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
if (appId == StreamConfiguration.INVALID_APP_ID) {
finish();
return;
}
// Add a launcher shortcut for this PC (forced, since this is user interaction)
shortcutHelper.createAppViewShortcut(uuid, pcName, uuid, true);
shortcutHelper.reportShortcutUsed(uuid);
// Initialize the MediaCodec helper before creating the decoder
MediaCodecHelper.initializeWithContext(this);
@@ -242,40 +254,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the connection
conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this));
keybTranslator = new KeyboardTranslator(conn);
controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
controllerHandler = new ControllerHandler(this, conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
inputManager.registerInputDeviceListener(controllerHandler, null);
boolean aspectRatioMatch = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
// setFixedSize so we can handle 4K properly. The only known devices that have
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
// on these devices. On Marshmallow, we can start changing to 4K manually but no
// 4K devices run 6.0 at the moment.
Display display = getWindowManager().getDefaultDisplay();
Point screenSize = new Point(0, 0);
display.getSize(screenSize);
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
LimeLog.info("Stream has compatible aspect ratio with output display");
aspectRatioMatch = true;
}
}
SurfaceHolder sh = streamView.getHolder();
if (prefConfig.stretchVideo || aspectRatioMatch) {
// Set the surface to the size of the video
sh.setFixedSize(prefConfig.width, prefConfig.height);
}
else {
// Set the surface to scale based on the aspect ratio of the stream
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
}
// Set to the optimal mode for streaming
prepareDisplayForRendering();
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
@@ -284,12 +269,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
streamView);
}
if (LimelightBuildProps.ROOT_BUILD) {
// Start watching for raw input
evdevHandler = new EvdevHandler(this, this);
evdevHandler.start();
// Use sustained performance mode on N+ to ensure consistent
// CPU availability
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
getWindow().setSustainedPerformanceMode(true);
}
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
if (prefConfig.onscreenController) {
// create virtual onscreen controller
virtualController = new VirtualController(conn,
@@ -305,7 +292,96 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// The connection will be started when the surface gets created
sh.addCallback(this);
streamView.getHolder().addCallback(this);
}
private void prepareDisplayForRendering() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode();
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate() &&
candidate.getRefreshRate() < 63;
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
candidate.getPhysicalWidth() <= 4096;
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
// On non-4K streams, we force the resolution to never change
if (prefConfig.width < 3840) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue;
}
}
// Make sure the refresh rate doesn't regress
if (!refreshRateOk) {
continue;
}
// Make sure the resolution doesn't regress
if (!resolutionOk) {
continue;
}
bestMode = candidate;
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
}
// On L, we can at least tell the OS that we want 60 Hz
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float bestRefreshRate = display.getRefreshRate();
for (float candidate : display.getSupportedRefreshRates()) {
if (candidate > bestRefreshRate && candidate < 63) {
LimeLog.info("Examining refresh rate: "+candidate);
bestRefreshRate = candidate;
}
}
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
}
// Apply the display mode change
getWindow().setAttributes(windowLayoutParams);
// From 4.4 to 5.1 we can't ask for a 4K display mode, so we'll
// need to hint the OS to provide one.
boolean aspectRatioMatch = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
// setFixedSize so we can handle 4K properly. The only known devices that have
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
// on these devices. On Marshmallow, we can start changing to 4K manually but no
// 4K devices run 6.0 at the moment.
Point screenSize = new Point(0, 0);
display.getSize(screenSize);
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
LimeLog.info("Stream has compatible aspect ratio with output display");
aspectRatioMatch = true;
}
}
if (prefConfig.stretchVideo || aspectRatioMatch) {
// Set the surface to the size of the video
streamView.getHolder().setFixedSize(prefConfig.width, prefConfig.height);
}
else {
// Set the surface to scale based on the aspect ratio of the stream
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
}
}
private void checkDataConnection()
@@ -406,16 +482,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void run() {
if (grabbedInput) {
NvMouseHelper.setCursorVisibility(Game.this, true);
if (evdevHandler != null) {
evdevHandler.ungrabAll();
}
inputCaptureProvider.disableCapture();
}
else {
NvMouseHelper.setCursorVisibility(Game.this, false);
if (evdevHandler != null) {
evdevHandler.regrabAll();
}
inputCaptureProvider.enableCapture();
}
grabbedInput = !grabbedInput;
@@ -522,11 +592,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return true;
}
// Eat repeat down events
if (event.getRepeatCount() > 0) {
return true;
}
// Pass through keyboard input if we're not grabbing
if (!grabbedInput) {
return super.onKeyDown(keyCode, event);
@@ -643,10 +708,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// Get relative axis values if we can
if (NvMouseHelper.eventHasRelativeMouseAxes(event)) {
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
// Send the deltas straight from the motion event
conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event),
(short)NvMouseHelper.getRelativeAxisY(event));
conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event),
(short) inputCaptureProvider.getRelativeAxisY(event));
// We have to also update the position Android thinks the cursor is at
// in order to avoid jumping when we stop moving or click.
@@ -823,14 +888,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
conn.stop();
}
// Close the Evdev reader to allow use of captured input devices
if (evdevHandler != null) {
evdevHandler.stop();
evdevHandler = null;
}
// Enable cursor visibility again
NvMouseHelper.setCursorVisibility(this, true);
inputCaptureProvider.disableCapture();
// Destroy the capture provider
inputCaptureProvider.destroy();
}
@Override
@@ -876,7 +938,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Hide the mouse cursor now. Doing it before
// dismissing the spinner seems to be undone
// when the spinner gets displayed.
NvMouseHelper.setCursorVisibility(Game.this, false);
inputCaptureProvider.enableCapture();
}
});
@@ -922,6 +984,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (connected) {
// HACK: Android is supposed to let you return from this function
// before throwing a fit if you access the surface again. Unfortunately,
// MediaCodec often tries to access the destroyed surface and triggers
// an IllegalStateException. To workaround this, we will invoke
// the DecoderRenderer's stop function ourselves, so it will hopefully
// happen early enough to not trigger the bug
decoderRenderer.stop();
stopConnection();
}
}
@@ -0,0 +1,79 @@
package com.limelight;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.limelight.utils.SpinnerDialog;
public class HelpActivity extends Activity {
private SpinnerDialog loadingDialog;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
webView = new WebView(this);
setContentView(webView);
// These allow the user to zoom the page
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setDisplayZoomControls(false);
// This sets the view to display the whole page by default
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
// This allows the links to places on the same page to work
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (loadingDialog == null) {
loadingDialog = SpinnerDialog.displayDialog(HelpActivity.this,
getResources().getString(R.string.help_loading_title),
getResources().getString(R.string.help_loading_msg), false);
}
}
@Override
public void onPageFinished(WebView view, String url) {
if (loadingDialog != null) {
loadingDialog.dismiss();
loadingDialog = null;
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.toUpperCase().startsWith("https://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()) ||
url.toUpperCase().startsWith("http://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase())) {
// Allow navigation to Moonlight docs
return false;
}
else {
return true;
}
}
});
webView.loadUrl(getIntent().getData().toString());
}
@Override
public void onBackPressed() {
// Back goes back through the WebView history
// until no more history remains
if (webView.canGoBack()) {
webView.goBack();
}
else {
super.onBackPressed();
}
}
}
+30 -2
View File
@@ -23,7 +23,9 @@ import com.limelight.preferences.StreamSettings;
import com.limelight.ui.AdapterFragment;
import com.limelight.ui.AdapterFragmentCallbacks;
import com.limelight.utils.Dialog;
import com.limelight.utils.HelpLauncher;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.UiHelper;
import android.app.Activity;
@@ -52,6 +54,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
public class PcView extends Activity implements AdapterFragmentCallbacks {
private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter;
private ShortcutHelper shortcutHelper;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private boolean freezeUpdates, runningPolling, inForeground;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@@ -109,6 +112,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Setup the list view
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
ImageButton helpButton = (ImageButton) findViewById(R.id.helpButton);
settingsButton.setOnClickListener(new OnClickListener() {
@Override
@@ -123,6 +127,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
startActivity(i);
}
});
helpButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
HelpLauncher.launchSetupGuide(PcView.this);
}
});
getFragmentManager().beginTransaction()
.replace(R.id.pcFragmentContainer, new AdapterFragment())
@@ -142,6 +152,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
shortcutHelper = new ShortcutHelper(this);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
@@ -299,7 +311,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Stop updates and wait while pairing
stopComputerUpdates(true);
InetAddress addr = null;
InetAddress addr;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp;
}
@@ -334,10 +346,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
else if (pairState == PairingManager.PairState.FAILED) {
message = getResources().getString(R.string.pair_fail);
}
else if (pairState == PairingManager.PairState.ALREADY_IN_PROGRESS) {
message = getResources().getString(R.string.pair_already_in_progress);
}
else if (pairState == PairingManager.PairState.PAIRED) {
// Just navigate to the app view without displaying a toast
message = null;
success = true;
// Invalidate reachability information after pairing to force
// a refresh before reading pair state again
managerBinder.invalidateStateForComputer(computer.uuid);
}
else {
// Should be no other values
@@ -429,7 +448,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
NvHTTP httpConn;
String message;
try {
InetAddress addr = null;
InetAddress addr;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp;
}
@@ -558,6 +577,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
if (details.equals(computer.details)) {
// Disable or delete shortcuts referencing this PC
shortcutHelper.disableShortcut(details.uuid.toString(),
getResources().getString(R.string.scut_deleted_pc));
pcGridAdapter.removeComputer(computer);
pcGridAdapter.notifyDataSetChanged();
@@ -584,6 +607,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}
}
// Add a launcher shortcut for this PC
if (details.pairState == PairState.PAIRED) {
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false);
}
if (existingEntry != null) {
// Replace the information in the existing entry
existingEntry.details = details;
@@ -1,24 +1,31 @@
package com.limelight.binding.input;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.SystemClock;
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.binding.input.driver.UsbDriverListener;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.input.MouseButtonPacket;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Vector2d;
import java.util.Timer;
import java.util.TimerTask;
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
private static final int START_DOWN_TIME_KEYB_MS = 750;
private static final int START_DOWN_TIME_MOUSE_MODE_MS = 750;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
@@ -34,6 +41,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final SparseArray<UsbDeviceContext> usbDeviceContexts = new SparseArray<>();
private final NvConnection conn;
private final Context activityContext;
private final double stickDeadzone;
private final InputDeviceContext defaultContext = new InputDeviceContext();
private final GameGestures gestures;
@@ -42,7 +50,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final boolean multiControllerEnabled;
private short currentControllers;
public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
this.activityContext = activityContext;
this.conn = conn;
this.gestures = gestures;
this.multiControllerEnabled = multiControllerEnabled;
@@ -342,6 +351,17 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
// The ADT-1 controller needs a similar fixup to the ASUS Gamepad
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
// The device name provided is just "Gamepad" which is pretty useless, so we
// use VID/PID instead
if (dev.getVendorId() == 0x18d1 && dev.getProductId() == 0x2c40) {
context.backIsStart = true;
context.modeIsSelect = true;
context.triggerDeadzone = 0.30f;
}
}
if (devName != null) {
// For the Nexus Player (and probably other ATV devices), we should
// use the back button as start since it doesn't have a start/menu button
@@ -384,6 +404,20 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.isServal = true;
context.ignoreBack = true;
}
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
// However, Microsoft released a firmware update with no change to VID/PID
// or device name that fixed the mappings for Android. Since there's
// no good way to detect this, we'll use the presence of GAS/BRAKE axes
// that were added in the latest firmware. If those are present, the only
// required fixup is ignoring the select button.
else if (devName.equals("Xbox Wireless Controller")) {
if (gasRange == null) {
context.isXboxBtController = true;
}
else {
context.ignoreBack = false;
}
}
}
LimeLog.info("Analog stick deadzone: "+context.leftStickDeadzoneRadius+" "+context.rightStickDeadzoneRadius);
@@ -392,21 +426,27 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return context;
}
private InputDeviceContext getContextForDevice(InputDevice dev) {
private InputDeviceContext getContextForEvent(InputEvent event) {
// Unknown devices use the default context
if (dev == null) {
if (event.getDeviceId() == 0) {
return defaultContext;
}
else if (event.getDevice() == null) {
// During device removal, sometimes we can get events after the
// input device has been destroyed. In this case we'll see a
// != 0 device ID but no device attached.
return null;
}
// Return the existing context if it exists
InputDeviceContext context = inputDeviceContexts.get(dev.getId());
InputDeviceContext context = inputDeviceContexts.get(event.getDeviceId());
if (context != null) {
return context;
}
// Otherwise create a new context
context = createInputDeviceContextForDevice(dev);
inputDeviceContexts.put(dev.getId(), context);
context = createInputDeviceContextForDevice(event.getDevice());
inputDeviceContexts.put(event.getDeviceId(), context);
return context;
}
@@ -451,7 +491,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// device before we send it.
for (int i = 0; i < inputDeviceContexts.size(); i++) {
GenericControllerContext context = inputDeviceContexts.valueAt(i);
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
if (context.assignedControllerNumber &&
context.controllerNumber == controllerNumber &&
context.mouseEmulationActive == originalContext.mouseEmulationActive) {
inputMap |= context.inputMap;
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
@@ -463,7 +505,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
for (int i = 0; i < usbDeviceContexts.size(); i++) {
GenericControllerContext context = usbDeviceContexts.valueAt(i);
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
if (context.assignedControllerNumber &&
context.controllerNumber == controllerNumber &&
context.mouseEmulationActive == originalContext.mouseEmulationActive) {
inputMap |= context.inputMap;
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
@@ -483,10 +527,40 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
}
conn.sendControllerInput(controllerNumber, inputMap,
leftTrigger, rightTrigger,
leftStickX, leftStickY,
rightStickX, rightStickY);
if (originalContext.mouseEmulationActive) {
int changedMask = inputMap ^ originalContext.mouseEmulationLastInputMap;
boolean aDown = (inputMap & ControllerPacket.A_FLAG) != 0;
boolean bDown = (inputMap & ControllerPacket.B_FLAG) != 0;
originalContext.mouseEmulationLastInputMap = inputMap;
if ((changedMask & ControllerPacket.A_FLAG) != 0) {
if (aDown) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
if ((changedMask & ControllerPacket.B_FLAG) != 0) {
if (bDown) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
conn.sendControllerInput(controllerNumber,
(short)0, (byte)0, (byte)0, (short)0, (short)0, (short)0, (short)0);
}
else {
conn.sendControllerInput(controllerNumber, inputMap,
leftTrigger, rightTrigger,
leftStickX, leftStickY,
rightStickX, rightStickY);
}
}
// Return a valid keycode, 0 to consume, or -1 to not consume the event
@@ -500,41 +574,38 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
if (context.isDualShock4) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1;
case KeyEvent.KEYCODE_BUTTON_Z:
return KeyEvent.KEYCODE_BUTTON_R1;
case KeyEvent.KEYCODE_BUTTON_C:
return KeyEvent.KEYCODE_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_X:
return KeyEvent.KEYCODE_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_B:
return KeyEvent.KEYCODE_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_A:
return KeyEvent.KEYCODE_BUTTON_X;
case KeyEvent.KEYCODE_BUTTON_SELECT:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case KeyEvent.KEYCODE_BUTTON_START:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case KeyEvent.KEYCODE_BUTTON_L2:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case KeyEvent.KEYCODE_BUTTON_R2:
return KeyEvent.KEYCODE_BUTTON_START;
// These are duplicate trigger events
case KeyEvent.KEYCODE_BUTTON_R1:
case KeyEvent.KEYCODE_BUTTON_L1:
return 0;
switch (event.getScanCode()) {
case 304:
return KeyEvent.KEYCODE_BUTTON_X;
case 305:
return KeyEvent.KEYCODE_BUTTON_A;
case 306:
return KeyEvent.KEYCODE_BUTTON_B;
case 307:
return KeyEvent.KEYCODE_BUTTON_Y;
case 308:
return KeyEvent.KEYCODE_BUTTON_L1;
case 309:
return KeyEvent.KEYCODE_BUTTON_R1;
/*
**** Using analog triggers instead ****
case 310:
return KeyEvent.KEYCODE_BUTTON_L2;
case 311:
return KeyEvent.KEYCODE_BUTTON_R2;
*/
case 312:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case 313:
return KeyEvent.KEYCODE_BUTTON_START;
case 314:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case 315:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case 316:
return KeyEvent.KEYCODE_BUTTON_MODE;
default:
return 0;
}
}
// If this is a Serval controller sending an unknown key code, it's probably
@@ -547,6 +618,35 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return KeyEvent.KEYCODE_BUTTON_START;
}
}
else if (context.isXboxBtController) {
switch (event.getScanCode()) {
case 306:
return KeyEvent.KEYCODE_BUTTON_X;
case 307:
return KeyEvent.KEYCODE_BUTTON_Y;
case 308:
return KeyEvent.KEYCODE_BUTTON_L1;
case 309:
return KeyEvent.KEYCODE_BUTTON_R1;
case 310:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case 311:
return KeyEvent.KEYCODE_BUTTON_START;
case 312:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case 313:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case 139:
return KeyEvent.KEYCODE_BUTTON_MODE;
default:
// Other buttons are mapped correctly
}
// The Xbox button is sent as MENU
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
return KeyEvent.KEYCODE_BUTTON_MODE;
}
}
if (context.hatXAxis != -1 && context.hatYAxis != -1) {
switch (event.getKeyCode()) {
@@ -561,7 +661,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
else if (context.hatXAxis == -1 &&
context.hatYAxis == -1 &&
context.isXboxController &&
/* FIXME: There's no good way to know for sure if xpad is bound
to this device, so we won't use the name to validate if these
scancodes should be mapped to DPAD
context.isXboxController &&
*/
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
// scan codes into proper key codes
@@ -651,9 +756,23 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
if (context.leftTriggerAxis != -1 && context.rightTriggerAxis != -1) {
// Android sends an initial 0 value for trigger axes even if the trigger
// should be negative when idle. After the first touch, the axes will go back
// to normal behavior, so ignore triggersIdleNegative for each trigger until
// first touch.
if (lt != 0) {
context.leftTriggerUsed = true;
}
if (rt != 0) {
context.rightTriggerUsed = true;
}
if (context.triggersIdleNegative) {
lt = (lt + 1) / 2;
rt = (rt + 1) / 2;
if (context.leftTriggerUsed) {
lt = (lt + 1) / 2;
}
if (context.rightTriggerUsed) {
rt = (rt + 1) / 2;
}
}
if (lt <= context.triggerDeadzone) {
@@ -689,7 +808,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
public boolean handleMotionEvent(MotionEvent event) {
InputDeviceContext context = getContextForDevice(event.getDevice());
InputDeviceContext context = getContextForEvent(event);
if (context == null) {
return true;
}
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
// We purposefully ignore the historical values in the motion event as it makes
@@ -720,8 +843,51 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return true;
}
private short scaleRawStickAxis(float stickValue) {
return (short)Math.pow(stickValue, 3);
}
private void sendEmulatedMouseEvent(short x, short y) {
Vector2d vector = new Vector2d();
vector.initialize(x, y);
vector.scalarMultiply(1 / 32766.0f);
vector.scalarMultiply(4);
if (vector.getMagnitude() > 0) {
// Move faster as the stick is pressed further from center
vector.scalarMultiply(Math.pow(vector.getMagnitude(), 2));
if (vector.getMagnitude() >= 1) {
conn.sendMouseMove((short)vector.getX(), (short)-vector.getY());
}
}
}
private void toggleMouseEmulation(final GenericControllerContext context) {
if (context.mouseEmulationTimer != null) {
context.mouseEmulationTimer.cancel();
context.mouseEmulationTimer = null;
}
context.mouseEmulationActive = !context.mouseEmulationActive;
Toast.makeText(activityContext, "Mouse emulation is: " + (context.mouseEmulationActive ? "ON" : "OFF"), Toast.LENGTH_SHORT).show();
if (context.mouseEmulationActive) {
context.mouseEmulationTimer = new Timer();
context.mouseEmulationTimer.schedule(new TimerTask() {
@Override
public void run() {
// Send mouse movement events from analog sticks
sendEmulatedMouseEvent(context.leftStickX, context.leftStickY);
sendEmulatedMouseEvent(context.rightStickX, context.rightStickY);
}
}, 50, 50);
}
}
public boolean handleButtonUp(KeyEvent event) {
InputDeviceContext context = getContextForDevice(event.getDevice());
InputDeviceContext context = getContextForEvent(event);
if (context == null) {
return true;
}
int keyCode = handleRemapping(context, event);
if (keyCode == 0) {
@@ -746,10 +912,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
break;
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
// FIXME: The stock keyboard doesn't have controller focus so isn't usable. I'm not enabling this shortcut
// until we have a custom keyboard or some other fix
//gestures.showKeyboard();
// Sometimes we'll get a spurious key up event on controller disconnect.
// Make sure it's real by checking that the key is actually down before taking
// any action.
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS) {
toggleMouseEmulation(context);
}
context.inputMap &= ~ControllerPacket.PLAY_FLAG;
break;
@@ -846,7 +1014,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
public boolean handleButtonDown(KeyEvent event) {
InputDeviceContext context = getContextForDevice(event.getDevice());
InputDeviceContext context = getContextForEvent(event);
if (context == null) {
return true;
}
int keyCode = handleRemapping(context, event);
if (keyCode == 0) {
@@ -938,11 +1109,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
// Send a new input packet if this is the first instance of a button down event
// or anytime if we're emulating a button
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
sendControllerInputPacket(context);
}
// We don't need to send repeat key down events, but the platform
// sends us events that claim to be repeats but they're from different
// devices, so we just send them all and deal with some duplicates.
sendControllerInputPacket(context);
return true;
}
@@ -1016,6 +1187,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public short rightStickY = 0x0000;
public short leftStickX = 0x0000;
public short leftStickY = 0x0000;
public boolean mouseEmulationActive;
public Timer mouseEmulationTimer;
public short mouseEmulationLastInputMap;
}
class InputDeviceContext extends GenericControllerContext {
@@ -1030,12 +1205,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public int leftTriggerAxis = -1;
public int rightTriggerAxis = -1;
public boolean triggersIdleNegative;
public boolean leftTriggerUsed, rightTriggerUsed;
public int hatXAxis = -1;
public int hatYAxis = -1;
public boolean isDualShock4;
public boolean isXboxController;
public boolean isXboxBtController;
public boolean isServal;
public boolean backIsStart;
public boolean modeIsSelect;
@@ -0,0 +1,59 @@
package com.limelight.binding.input.capture;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
import android.view.ViewGroup;
@TargetApi(Build.VERSION_CODES.N)
public class AndroidCaptureProvider extends InputCaptureProvider {
private ViewGroup rootViewGroup;
private Context context;
public AndroidCaptureProvider(Activity activity) {
this.context = activity;
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
}
public static boolean isCaptureProviderSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
private void setPointerIconOnAllViews(PointerIcon icon) {
for (int i = 0; i < rootViewGroup.getChildCount(); i++) {
View view = rootViewGroup.getChildAt(i);
view.setPointerIcon(icon);
}
rootViewGroup.setPointerIcon(icon);
}
@Override
public void enableCapture() {
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
}
@Override
public void disableCapture() {
setPointerIconOnAllViews(null);
}
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X) != 0 ||
event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y) != 0;
}
@Override
public float getRelativeAxisX(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
}
@Override
public float getRelativeAxisY(MotionEvent event) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
}
}
@@ -0,0 +1,31 @@
package com.limelight.binding.input.capture;
import android.app.Activity;
import com.limelight.LimeLog;
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevListener;
public class InputCaptureManager {
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
// Shield capture is preferred because it can capture when the cursor is over
// the system UI. Android N native capture can only capture over views owned
// by the application.
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Evdev mouse capture");
return new EvdevCaptureProvider(activity, rootListener);
}
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Android N+ native mouse capture");
return new AndroidCaptureProvider(activity);
}
else {
LimeLog.info("Mouse capture not available");
return new NullCaptureProvider();
}
}
}
@@ -0,0 +1,21 @@
package com.limelight.binding.input.capture;
import android.view.MotionEvent;
public abstract class InputCaptureProvider {
public abstract void enableCapture();
public abstract void disableCapture();
public void destroy() {}
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return false;
}
public float getRelativeAxisX(MotionEvent event) {
return 0;
}
public float getRelativeAxisY(MotionEvent event) {
return 0;
}
}
@@ -0,0 +1,10 @@
package com.limelight.binding.input.capture;
public class NullCaptureProvider extends InputCaptureProvider {
@Override
public void enableCapture() {}
@Override
public void disableCapture() {}
}
@@ -1,12 +1,10 @@
package com.limelight.binding.input;
package com.limelight.binding.input.capture;
import android.content.Context;
import android.hardware.input.InputManager;
import android.view.MotionEvent;
import com.limelight.LimeLog;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -18,12 +16,14 @@ import java.lang.reflect.Method;
//
// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm
public class NvMouseHelper {
public class ShieldCaptureProvider extends InputCaptureProvider {
private static boolean nvExtensionSupported;
private static Method methodSetCursorVisibility;
private static int AXIS_RELATIVE_X;
private static int AXIS_RELATIVE_Y;
private Context context;
static {
try {
methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
@@ -36,16 +36,19 @@ public class NvMouseHelper {
nvExtensionSupported = true;
} catch (Exception e) {
LimeLog.info("NvMouseHelper not supported");
nvExtensionSupported = false;
}
}
public static boolean setCursorVisibility(Context context, boolean visible) {
if (!nvExtensionSupported) {
return false;
}
public ShieldCaptureProvider(Context context) {
this.context = context;
}
public static boolean isCaptureProviderSupported() {
return nvExtensionSupported;
}
private boolean setCursorVisibility(boolean visible) {
try {
methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible);
return true;
@@ -58,19 +61,29 @@ public class NvMouseHelper {
return false;
}
public static boolean eventHasRelativeMouseAxes(MotionEvent event) {
if (!nvExtensionSupported) {
return false;
}
@Override
public void enableCapture() {
setCursorVisibility(false);
}
@Override
public void disableCapture() {
setCursorVisibility(true);
}
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
event.getAxisValue(AXIS_RELATIVE_Y) != 0;
}
public static float getRelativeAxisX(MotionEvent event) {
@Override
public float getRelativeAxisX(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X);
}
public static float getRelativeAxisY(MotionEvent event) {
@Override
public float getRelativeAxisY(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_Y);
}
}
@@ -1,13 +1,5 @@
package com.limelight.binding.input.driver;
import android.hardware.usb.UsbEndpoint;
import com.limelight.LimeLog;
import com.limelight.binding.video.MediaCodecHelper;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public abstract class AbstractController {
private final int deviceId;
@@ -114,13 +114,13 @@ public abstract class AbstractXboxController extends AbstractController {
return false;
}
// Report that we're added _before_ starting the input thread
notifyDeviceAdded();
// Start listening for controller input
inputThread = createInputThread();
inputThread.start();
// Now report we're added
notifyDeviceAdded();
return true;
}
@@ -137,11 +137,11 @@ public abstract class AbstractXboxController extends AbstractController {
inputThread = null;
}
// Report the device removed
notifyDeviceRemoved();
// Close the USB connection
connection.close();
// Report the device removed
notifyDeviceRemoved();
}
protected abstract boolean handleRead(ByteBuffer buffer);
@@ -72,14 +72,14 @@ public class UsbDriverService extends Service implements UsbDriverListener {
// Initial attachment broadcast
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// Continue the state machine
handleUsbDeviceState(device);
}
// Subsequent permission dialog completion intent
else if (action.equals(ACTION_USB_PERMISSION)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// If we got this far, we've already found we're able to handle this device
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
@@ -10,56 +10,38 @@ import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer;
public class Xbox360Controller extends AbstractXboxController {
private static final int XB360_IFACE_SUBCLASS = 93;
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
// This list is taken from the Xpad driver in the Linux kernel.
// I've excluded the devices that aren't "controllers" in the traditional sense, but
// if people really want to use their dancepads or fight sticks with Moonlight, I can
// put them in.
private static final DeviceIdTuple[] supportedDeviceTuples = {
new DeviceIdTuple(0x045e, 0x028e, "Microsoft X-Box 360 pad"),
new DeviceIdTuple(0x044f, 0xb326, "Thrustmaster Gamepad GP XID"),
new DeviceIdTuple(0x046d, 0xc21d, "Logitech Gamepad F310"),
new DeviceIdTuple(0x046d, 0xc21e, "Logitech Gamepad F510"),
new DeviceIdTuple(0x046d, 0xc21f, "Logitech Gamepad F710"),
new DeviceIdTuple(0x046d, 0xc242, "Logitech Chillstream Controller"),
new DeviceIdTuple(0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller"),
new DeviceIdTuple(0x0738, 0x4726, "Mad Catz Xbox 360 Controller"),
new DeviceIdTuple(0x0738, 0xb726, "Mad Catz Xbox controller - MW2"),
new DeviceIdTuple(0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad"),
new DeviceIdTuple(0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360"),
new DeviceIdTuple(0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller"),
new DeviceIdTuple(0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360"),
new DeviceIdTuple(0x0e6f, 0x0301, "Logic3 Controller"),
new DeviceIdTuple(0x0e6f, 0x0401, "Logic3 Controller"),
new DeviceIdTuple(0x12ab, 0x0301, "PDP AFTERGLOW AX.1"),
new DeviceIdTuple(0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller"),
new DeviceIdTuple(0x1532, 0x0037, "Razer Sabertooth"),
new DeviceIdTuple(0x15e4, 0x3f00, "Power A Mini Pro Elite"),
new DeviceIdTuple(0x15e4, 0x3f0a, "Xbox Airflo wired controller"),
new DeviceIdTuple(0x15e4, 0x3f10, "Batarang Xbox 360 controller"),
new DeviceIdTuple(0x162e, 0xbeef, "Joytech Neo-Se Take2"),
new DeviceIdTuple(0x1689, 0xfd00, "Razer Onza Tournament Edition"),
new DeviceIdTuple(0x1689, 0xfd01, "Razer Onza Classic Edition"),
new DeviceIdTuple(0x24c6, 0x5d04, "Razer Sabertooth"),
new DeviceIdTuple(0x1bad, 0xf016, "Mad Catz Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)"),
new DeviceIdTuple(0x1bad, 0xf900, "Harmonix Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf901, "Gamestop Xbox 360 Controller"),
new DeviceIdTuple(0x1bad, 0xf903, "Tron Xbox 360 controller"),
new DeviceIdTuple(0x24c6, 0x5300, "PowerA MINI PROEX Controller"),
new DeviceIdTuple(0x24c6, 0x5303, "Xbox Airflo wired controller"),
private static final int[] SUPPORTED_VENDORS = {
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
0x0738, // Mad Catz
0x0e6f, // Unknown
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
0x1bad, // Harmonix
0x0f0d, // Hori
0x1689, // Razer Onza
0x24c6, // PowerA
0x1532, // Razer Sabertooth
0x15e4, // Numark
0x162e, // Joytech
};
public static boolean canClaimDevice(UsbDevice device) {
for (DeviceIdTuple tuple : supportedDeviceTuples) {
if (device.getVendorId() == tuple.vid && device.getProductId() == tuple.pid) {
LimeLog.info("XB360 can claim device: " + tuple.name);
for (int supportedVid : SUPPORTED_VENDORS) {
if (device.getVendorId() == supportedVid &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB360_IFACE_PROTOCOL) {
return true;
}
}
return false;
}
@@ -151,16 +133,4 @@ public class Xbox360Controller extends AbstractXboxController {
// No need to fail init if the LED command fails
return true;
}
private static class DeviceIdTuple {
public final int vid;
public final int pid;
public final String name;
public DeviceIdTuple(int vid, int pid, String name) {
this.vid = vid;
this.pid = pid;
this.name = name;
}
}
}
@@ -3,22 +3,25 @@ package com.limelight.binding.input.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import com.limelight.LimeLog;
import com.limelight.binding.video.MediaCodecHelper;
import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class XboxOneController extends AbstractXboxController {
private static final int MICROSOFT_VID = 0x045e;
private static final int XB1_IFACE_SUBCLASS = 71;
private static final int XB1_IFACE_PROTOCOL = 208;
private static final int[] SUPPORTED_VENDORS = {
0x045e, // Microsoft
0x0738, // Mad Catz
0x0e6f, // Unknown
0x0f0d, // Hori
0x24c6, // PowerA
};
// FIXME: odata_serial
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
@@ -59,6 +62,12 @@ public class XboxOneController extends AbstractXboxController {
rightStickY = ~buffer.getShort() / 32767.0f;
}
private void ackModeReport(byte seqNum) {
byte[] payload = {0x01, 0x20, seqNum, 0x09, 0x00, 0x07, 0x20, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00};
connection.bulkTransfer(outEndpt, payload, payload.length, 3000);
}
@Override
protected boolean handleRead(ByteBuffer buffer) {
switch (buffer.get())
@@ -69,7 +78,15 @@ public class XboxOneController extends AbstractXboxController {
return true;
case 0x07:
buffer.position(buffer.position() + 3);
// The Xbox One S controller needs acks for mode reports otherwise
// it retransmits them forever.
if (buffer.get() == 0x30) {
ackModeReport(buffer.get());
buffer.position(buffer.position() + 1);
}
else {
buffer.position(buffer.position() + 2);
}
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
return true;
}
@@ -78,11 +95,17 @@ public class XboxOneController extends AbstractXboxController {
}
public static boolean canClaimDevice(UsbDevice device) {
return device.getVendorId() == MICROSOFT_VID &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
for (int supportedVid : SUPPORTED_VENDORS) {
if (device.getVendorId() == supportedVid &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
return true;
}
}
return false;
}
@Override
@@ -1,9 +1,12 @@
package com.limelight.binding.input.evdev;
import android.app.Activity;
import android.os.Build;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.binding.input.capture.InputCaptureProvider;
import java.io.DataOutputStream;
import java.io.File;
@@ -13,7 +16,7 @@ import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class EvdevHandler {
public class EvdevCaptureProvider extends InputCaptureProvider {
private final EvdevListener listener;
private final String libraryPath;
@@ -25,6 +28,7 @@ public class EvdevHandler {
private ServerSocket servSock;
private Socket evdevSock;
private Activity activity;
private boolean started = false;
private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2;
@@ -44,30 +48,42 @@ public class EvdevHandler {
return;
}
// Launch a su shell
ProcessBuilder builder = new ProcessBuilder("su");
builder.redirectErrorStream(true);
final String evdevReaderCmd = libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort();
try {
su = builder.start();
} catch (IOException e) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
}
});
e.printStackTrace();
return;
// On Nougat and later, we'll need to pass the command directly to SU.
// Writing to SU's input stream after it has started doesn't seem to work anymore.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Launch evdev_reader directly via SU
try {
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd);
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
}
else {
// Launch a SU shell on Marshmallow and earlier
ProcessBuilder builder = new ProcessBuilder("su");
builder.redirectErrorStream(true);
// Start evdevreader
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
try {
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
} catch (IOException e) {
e.printStackTrace();
return;
try {
su = builder.start();
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
// Start evdevreader
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
try {
suOut.writeChars(evdevReaderCmd+"\n");
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
}
// Wait for evdevreader's connection
@@ -163,24 +179,48 @@ public class EvdevHandler {
}
};
public EvdevHandler(Activity activity, EvdevListener listener) {
public EvdevCaptureProvider(Activity activity, EvdevListener listener) {
this.listener = listener;
this.activity = activity;
this.libraryPath = activity.getApplicationInfo().nativeLibraryDir;
}
public void regrabAll() {
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
}
private void reportDeviceNotRooted() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
}
});
}
@Override
public void enableCapture() {
if (!started) {
// Start the handler thread if it's our first time
// capturing
handlerThread.start();
started = true;
}
else {
// Send a request to regrab if we're already capturing
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void ungrabAll() {
if (!shutdown && evdevOut != null) {
@Override
public void disableCapture() {
if (started && !shutdown && evdevOut != null) {
try {
evdevOut.write(UNGRAB_REQUEST);
} catch (IOException e) {
@@ -189,15 +229,16 @@ public class EvdevHandler {
}
}
public void start() {
handlerThread.start();
}
public void stop() {
@Override
public void destroy() {
// We need to stop the process in this context otherwise
// we could get stuck waiting on output from the process
// in order to terminate it.
if (!started) {
return;
}
shutdown = true;
handlerThread.interrupt();
@@ -52,7 +52,7 @@ public class DigitalButton extends VirtualControllerElement {
}
}
private List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>();
private List<DigitalButtonListener> listeners = new ArrayList<>();
private String text = "";
private int icon = -1;
private long timerLongClickTimeout = 3000;
@@ -20,7 +20,7 @@ public class DigitalPad extends VirtualControllerElement {
public final static int DIGITAL_PAD_DIRECTION_UP = 2;
public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4;
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8;
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>();
List<DigitalPadListener> listeners = new ArrayList<>();
private static final int DPAD_MARGIN = 5;
@@ -48,7 +48,7 @@ public class VirtualController {
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
private Button buttonConfigure = null;
private List<VirtualControllerElement> elements = new ArrayList<VirtualControllerElement>();
private List<VirtualControllerElement> elements = new ArrayList<>();
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) {
this.connection = conn;
@@ -60,7 +60,7 @@ public class VirtualController {
frame_layout.addView(relative_layout);
buttonConfigure = new Button(context);
buttonConfigure.setBackgroundResource(R.drawable.settings);
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
buttonConfigure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -25,6 +25,8 @@ import android.view.SurfaceHolder;
public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private static final boolean USE_FRAME_RENDER_TIME = false;
// Used on versions < 5.0
private ByteBuffer[] legacyInputBuffers;
@@ -202,9 +204,31 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Operate at maximum rate to lower latency as much as possible on
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
// but that will actually result in the decoder crashing if it can't satisfy
// our (ludicrous) operating rate requirement.
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
}
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
@Override
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
if (delta >= 0 && delta < 1000) {
if (USE_FRAME_RENDER_TIME) {
totalTimeMs += delta;
}
}
}
}, null);
}
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
return true;
@@ -265,7 +289,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta;
totalTimeMs += delta;
if (!USE_FRAME_RENDER_TIME) {
totalTimeMs += delta;
}
}
} else {
switch (outIndex) {
@@ -413,7 +439,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
long delta = MediaCodecHelper.getMonotonicMillis()-(presentationTimeUs/1000);
if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta;
totalTimeMs += delta;
if (!USE_FRAME_RENDER_TIME) {
totalTimeMs += delta;
}
}
} else {
switch (outIndex) {
@@ -475,8 +503,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} catch (InterruptedException ignored) { }
}
// Stop the decoder
videoDecoder.stop();
// We could stop the decoder here, but it seems to cause some problems
// so we'll just let release take care of it.
}
@Override
@@ -34,7 +34,7 @@ public class MediaCodecHelper {
private static final List<String> whitelistedHevcDecoders;
static {
directSubmitPrefixes = new LinkedList<String>();
directSubmitPrefixes = new LinkedList<>();
// These decoders have low enough input buffer latency that they
// can be directly invoked from the receive thread
@@ -48,11 +48,11 @@ public class MediaCodecHelper {
}
static {
preferredDecoders = new LinkedList<String>();
preferredDecoders = new LinkedList<>();
}
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
blacklistedDecoderPrefixes = new LinkedList<>();
// Software decoders that don't support H264 high profile
blacklistedDecoderPrefixes.add("omx.google");
@@ -65,21 +65,21 @@ public class MediaCodecHelper {
}
static {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList<String>();
baselineProfileHackPrefixes = new LinkedList<>();
baselineProfileHackPrefixes.add("omx.intel");
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
whitelistedAdaptiveResolutionPrefixes = new LinkedList<>();
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
constrainedHighProfilePrefixes = new LinkedList<String>();
constrainedHighProfilePrefixes = new LinkedList<>();
constrainedHighProfilePrefixes.add("omx.intel");
}
@@ -218,7 +218,7 @@ public class MediaCodecHelper {
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
LinkedList<MediaCodecInfo> infoList = new LinkedList<MediaCodecInfo>();
LinkedList<MediaCodecInfo> infoList = new LinkedList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
@@ -376,7 +376,7 @@ public class MediaCodecHelper {
break;
cpuInfo.append((char)ch);
}
return cpuInfo.toString();
} finally {
br.close();
@@ -68,7 +68,7 @@ public class ComputerDatabaseManager {
public List<ComputerDetails> getAllComputers() {
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
LinkedList<ComputerDetails> computerList = new LinkedList<>();
while (c.moveToNext()) {
ComputerDetails details = new ComputerDetails();
@@ -37,7 +37,9 @@ public class ComputerManagerService extends Service {
private static final int MDNS_QUERY_PERIOD_MS = 1000;
private static final int FAST_POLL_TIMEOUT = 500;
private static final int OFFLINE_POLL_TRIES = 5;
private static final int INITIAL_POLL_TRIES = 2;
private static final int EMPTY_LIST_THRESHOLD = 3;
private static final int POLL_DATA_TTL_MS = 30000;
private final ComputerManagerBinder binder = new ComputerManagerBinder();
@@ -45,7 +47,7 @@ public class ComputerManagerService extends Service {
private final AtomicInteger dbRefCount = new AtomicInteger(0);
private IdentityManager idManager;
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<>();
private ComputerManagerListener listener = null;
private final AtomicInteger activePolls = new AtomicInteger(0);
private boolean pollingActive = false;
@@ -76,12 +78,15 @@ public class ComputerManagerService extends Service {
return false;
}
final int pollTriesBeforeOffline = details.state == ComputerDetails.State.UNKNOWN ?
INITIAL_POLL_TRIES : OFFLINE_POLL_TRIES;
activePolls.incrementAndGet();
// Poll the machine
try {
if (!pollComputer(details)) {
if (!newPc && offlineCount < OFFLINE_POLL_TRIES) {
if (!newPc && offlineCount < pollTriesBeforeOffline) {
// Return without calling the listener
releaseLocalDatabaseReference();
return false;
@@ -136,6 +141,7 @@ public class ComputerManagerService extends Service {
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
offlineCount++;
} else {
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
offlineCount = 0;
}
}
@@ -165,11 +171,18 @@ public class ComputerManagerService extends Service {
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
// Enforce the poll data TTL
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN;
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
}
// Report this computer initially
listener.notifyComputerUpdated(tuple.computer);
// This polling thread might already be there
if (tuple.thread == null) {
// Report this computer initially
listener.notifyComputerUpdated(tuple.computer);
tuple.thread = createPollingThread(tuple);
tuple.thread.start();
}
@@ -229,6 +242,21 @@ public class ComputerManagerService extends Service {
return null;
}
public void invalidateStateForComputer(UUID uuid) {
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) {
// We need the network lock to prevent a concurrent poll
// from wiping this change out
synchronized (tuple.networkLock) {
tuple.computer.state = ComputerDetails.State.UNKNOWN;
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
}
}
}
}
}
}
@Override
@@ -778,6 +806,7 @@ class PollingTuple {
public Thread thread;
public final ComputerDetails computer;
public final Object networkLock;
public long lastSuccessfulPollMs;
public PollingTuple(ComputerDetails computer, Thread thread) {
this.computer = computer;
@@ -3,6 +3,7 @@ package com.limelight.grid;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.limelight.AppView;
@@ -14,8 +15,6 @@ import com.limelight.grid.assets.MemoryAssetLoader;
import com.limelight.grid.assets.NetworkAssetLoader;
import com.limelight.nvstream.http.ComputerDetails;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Comparator;
@@ -28,7 +27,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private final CachedAppAssetLoader loader;
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) {
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item));
int dpi = activity.getResources().getDisplayMetrics().densityDpi;
int dp;
@@ -53,9 +52,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
new NetworkAssetLoader(context, uniqueId),
new MemoryAssetLoader(),
new DiskAssetLoader(context.getCacheDir()),
BitmapFactory.decodeResource(activity.getResources(),
R.drawable.image_loading, options));
new DiskAssetLoader(context.getCacheDir()));
}
public void cancelQueuedOperations() {
@@ -68,7 +65,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
@Override
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
return lhs.app.getAppName().compareTo(rhs.app.getAppName());
return lhs.app.getAppName().toLowerCase().compareTo(rhs.app.getAppName().toLowerCase());
}
});
}
@@ -86,9 +83,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
itemList.remove(app);
}
public boolean populateImageView(ImageView imgView, AppView.AppObject obj) {
@Override
public boolean populateImageView(ImageView imgView, ProgressBar prgView, AppView.AppObject obj) {
// Let the cached asset loader handle it
loader.populateImageView(obj.app, imgView);
loader.populateImageView(obj.app, imgView, prgView);
return true;
}
@@ -103,9 +101,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
@Override
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
if (obj.app.getIsRunning()) {
if (obj.isRunning) {
// Show the play button overlay
overlayView.setImageResource(R.drawable.play);
overlayView.setImageResource(R.drawable.ic_play);
return true;
}
@@ -6,6 +6,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.limelight.R;
@@ -14,15 +15,13 @@ import java.util.ArrayList;
public abstract class GenericGridAdapter<T> extends BaseAdapter {
protected final Context context;
protected final int defaultImageRes;
protected final int layoutId;
protected final ArrayList<T> itemList = new ArrayList<T>();
protected final ArrayList<T> itemList = new ArrayList<>();
protected final LayoutInflater inflater;
public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) {
public GenericGridAdapter(Context context, int layoutId) {
this.context = context;
this.layoutId = layoutId;
this.defaultImageRes = defaultImageRes;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@@ -46,7 +45,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
return i;
}
public abstract boolean populateImageView(ImageView imgView, T obj);
public abstract boolean populateImageView(ImageView imgView, ProgressBar prgView, T obj);
public abstract boolean populateTextView(TextView txtView, T obj);
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
@@ -59,10 +58,11 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
ImageView imgView = (ImageView) convertView.findViewById(R.id.grid_image);
ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay);
TextView txtView = (TextView) convertView.findViewById(R.id.grid_text);
ProgressBar prgView = (ProgressBar) convertView.findViewById(R.id.grid_spinner);
if (imgView != null) {
if (!populateImageView(imgView, itemList.get(i))) {
imgView.setImageResource(defaultImageRes);
if (!populateImageView(imgView, prgView, itemList.get(i))) {
imgView.setImageBitmap(null);
}
}
if (!populateTextView(txtView, itemList.get(i))) {
@@ -1,7 +1,9 @@
package com.limelight.grid;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.limelight.PcView;
@@ -14,7 +16,7 @@ import java.util.Comparator;
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
public PcGridAdapter(Context context, boolean listMode, boolean small) {
super(context, listMode ? R.layout.simple_row : (small ? R.layout.pc_grid_item_small : R.layout.pc_grid_item), R.drawable.computer);
super(context, listMode ? R.layout.simple_row : (small ? R.layout.pc_grid_item_small : R.layout.pc_grid_item));
}
public void addComputer(PcView.ComputerObject computer) {
@@ -26,7 +28,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
@Override
public int compare(PcView.ComputerObject lhs, PcView.ComputerObject rhs) {
return lhs.details.name.compareTo(rhs.details.name);
return lhs.details.name.toLowerCase().compareTo(rhs.details.name.toLowerCase());
}
});
}
@@ -36,21 +38,28 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
}
@Override
public boolean populateImageView(ImageView imgView, PcView.ComputerObject obj) {
if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
public boolean populateImageView(ImageView imgView, ProgressBar prgView, PcView.ComputerObject obj) {
if (obj.details.state == ComputerDetails.State.ONLINE) {
imgView.setAlpha(1.0f);
}
else {
imgView.setAlpha(0.4f);
}
// Return false to use the default drawable
return false;
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
prgView.setVisibility(View.VISIBLE);
}
else {
prgView.setVisibility(View.INVISIBLE);
}
imgView.setImageResource(R.drawable.ic_computer);
return true;
}
@Override
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
if (obj.details.state == ComputerDetails.State.ONLINE) {
txtView.setAlpha(1.0f);
}
else {
@@ -63,13 +72,11 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
@Override
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Still refreshing this PC so display the overlay
overlayView.setImageResource(R.drawable.image_loading);
if (obj.details.state == ComputerDetails.State.OFFLINE) {
overlayView.setImageResource(R.drawable.ic_pc_offline);
overlayView.setAlpha(0.4f);
return true;
}
// No overlay
return false;
}
}
@@ -5,7 +5,9 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
@@ -53,13 +55,13 @@ public class CachedAppAssetLoader {
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
DiskAssetLoader diskLoader, Bitmap placeholderBitmap) {
DiskAssetLoader diskLoader) {
this.computer = computer;
this.scalingDivider = scalingDivider;
this.networkLoader = networkLoader;
this.memoryLoader = memoryLoader;
this.diskLoader = diskLoader;
this.placeholderBitmap = placeholderBitmap;
this.placeholderBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
}
public void cancelBackgroundLoads() {
@@ -130,12 +132,14 @@ public class CachedAppAssetLoader {
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
private final WeakReference<ImageView> imageViewRef;
private final WeakReference<ProgressBar> progressViewRef;
private final boolean diskOnly;
private LoaderTuple tuple;
public LoaderTask(ImageView imageView, boolean diskOnly) {
this.imageViewRef = new WeakReference<ImageView>(imageView);
public LoaderTask(ImageView imageView, ProgressBar prgView, boolean diskOnly) {
this.imageViewRef = new WeakReference<>(imageView);
this.progressViewRef = new WeakReference<>(prgView);
this.diskOnly = diskOnly;
}
@@ -143,8 +147,8 @@ public class CachedAppAssetLoader {
protected Bitmap doInBackground(LoaderTuple... params) {
tuple = params[0];
// Check whether it has been cancelled or the image view is gone
if (isCancelled() || imageViewRef.get() == null) {
// Check whether it has been cancelled or the views are gone
if (isCancelled() || imageViewRef.get() == null || progressViewRef.get() == null) {
return null;
}
@@ -177,11 +181,17 @@ public class CachedAppAssetLoader {
// If the current loader task for this view isn't us, do nothing
final ImageView imageView = imageViewRef.get();
final ProgressBar prgView = progressViewRef.get();
if (getLoaderTask(imageView) == this) {
// Now display the progress bar since we have to hit the network
if (prgView != null) {
prgView.setVisibility(View.VISIBLE);
}
// Set off another loader task on the network executor
LoaderTask task = new LoaderTask(imageView, false);
LoaderTask task = new LoaderTask(imageView, prgView, false);
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), placeholderBitmap, task);
imageView.setAlpha(1.0f);
imageView.setVisibility(View.VISIBLE);
imageView.setImageDrawable(asyncDrawable);
task.executeOnExecutor(networkExecutor, tuple);
}
@@ -195,14 +205,20 @@ public class CachedAppAssetLoader {
}
final ImageView imageView = imageViewRef.get();
final ProgressBar prgView = progressViewRef.get();
if (getLoaderTask(imageView) == this) {
// Set the bitmap
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// Hide the progress bar
if (prgView != null) {
prgView.setVisibility(View.INVISIBLE);
}
// Show the view
imageView.setAlpha(1.0f);
imageView.setVisibility(View.VISIBLE);
}
}
}
@@ -213,7 +229,7 @@ public class CachedAppAssetLoader {
public AsyncDrawable(Resources res, Bitmap bitmap,
LoaderTask loaderTask) {
super(res, bitmap);
loaderTaskReference = new WeakReference<LoaderTask>(loaderTask);
loaderTaskReference = new WeakReference<>(loaderTask);
}
public LoaderTask getLoaderTask() {
@@ -280,34 +296,38 @@ public class CachedAppAssetLoader {
});
}
public void populateImageView(NvApp app, ImageView view) {
public boolean populateImageView(NvApp app, ImageView imgView, ProgressBar prgView) {
LoaderTuple tuple = new LoaderTuple(computer, app);
// If there's already a task in progress for this view,
// cancel it. If the task is already loading the same image,
// we return and let that load finish.
if (!cancelPendingLoad(tuple, view)) {
return;
if (!cancelPendingLoad(tuple, imgView)) {
return true;
}
// Hide the progress bar always on initial load
prgView.setVisibility(View.INVISIBLE);
// First, try the memory cache in the current context
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
if (bmp != null) {
// Show the bitmap immediately
view.setAlpha(1.0f);
view.setImageBitmap(bmp);
return;
imgView.setVisibility(View.VISIBLE);
imgView.setImageBitmap(bmp);
return true;
}
// If it's not in memory, create an async task to load it. This task will be attached
// via AsyncDrawable to this view.
final LoaderTask task = new LoaderTask(view, true);
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
view.setAlpha(0.0f);
view.setImageDrawable(asyncDrawable);
final LoaderTask task = new LoaderTask(imgView, prgView, true);
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
imgView.setVisibility(View.INVISIBLE);
imgView.setImageDrawable(asyncDrawable);
// Run the task on our foreground executor
task.executeOnExecutor(foregroundExecutor, tuple);
return false;
}
public class LoaderTuple {
@@ -7,7 +7,6 @@ import com.limelight.LimeLog;
import com.limelight.utils.CacheHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -29,7 +29,7 @@ import android.widget.Toast;
public class AddComputerManually extends Activity {
private TextView hostText;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<>();
private Thread addThread;
private final ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, final IBinder binder) {
@@ -3,6 +3,7 @@ package com.limelight.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.preference.PreferenceManager;
@@ -100,7 +101,7 @@ public class PreferenceConfiguration {
}
// Use small mode on anything smaller than a 7" tablet
return context.getResources().getConfiguration().screenWidthDp < 600;
return context.getResources().getConfiguration().smallestScreenWidthDp < 500;
}
public static int getDefaultBitrate(Context context) {
@@ -2,7 +2,6 @@ package com.limelight.preferences;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.app.Activity;
@@ -5,6 +5,9 @@ import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.widget.Button;
import com.limelight.R;
public class Dialog implements Runnable {
private final String title;
@@ -14,7 +17,7 @@ public class Dialog implements Runnable {
private AlertDialog alert;
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<>();
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
@@ -55,7 +58,7 @@ public class Dialog implements Runnable {
alert.setCancelable(false);
alert.setCanceledOnTouchOutside(false);
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
alert.setButton(AlertDialog.BUTTON_POSITIVE, activity.getResources().getText(android.R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
synchronized (rundownDialogs) {
rundownDialogs.remove(Dialog.this);
@@ -67,6 +70,31 @@ public class Dialog implements Runnable {
}
}
});
alert.setButton(AlertDialog.BUTTON_NEUTRAL, activity.getResources().getText(R.string.help), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
synchronized (rundownDialogs) {
rundownDialogs.remove(Dialog.this);
alert.dismiss();
}
if (endAfterDismiss) {
activity.finish();
}
HelpLauncher.launchTroubleshooting(activity);
}
});
alert.setOnShowListener(new DialogInterface.OnShowListener(){
@Override
public void onShow(DialogInterface dialog) {
// Set focus to the OK button by default
Button button = alert.getButton(AlertDialog.BUTTON_POSITIVE);
button.setFocusable(true);
button.setFocusableInTouchMode(true);
button.requestFocus();
}
});
synchronized (rundownDialogs) {
rundownDialogs.add(this);
@@ -0,0 +1,47 @@
package com.limelight.utils;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import com.limelight.HelpActivity;
public class HelpLauncher {
private static void launchUrl(Context context, String url) {
// Try to launch the default browser
try {
// Fire TV devices will lie and say they do have a browser
// even though the OS just shows an error dialog if we
// try to use it.
if (!"Amazon".equalsIgnoreCase(Build.MANUFACTURER)) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
context.startActivity(i);
return;
}
} catch (Exception e) {
// This is only supposed to throw ActivityNotFoundException but
// it can (at least) also throw SecurityException if a user's default
// browser is not exported. We'll catch everything to workaround this.
// Fall through
}
// This platform has no browser (possibly a leanback device)
// We'll launch our WebView activity
Intent i = new Intent(context, HelpActivity.class);
i.setData(Uri.parse(url));
context.startActivity(i);
}
public static void launchSetupGuide(Context context) {
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
}
public static void launchTroubleshooting(Context context) {
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
}
}
@@ -23,8 +23,8 @@ public class ServerHelper {
computer.localIp : computer.remoteIp;
}
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
ComputerManagerService.ComputerManagerBinder managerBinder) {
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
ComputerManagerService.ComputerManagerBinder managerBinder) {
Intent intent = new Intent(parent, Game.class);
intent.putExtra(Game.EXTRA_HOST,
computer.reachability == ComputerDetails.Reachability.LOCAL ?
@@ -34,7 +34,14 @@ public class ServerHelper {
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
computer.reachability != ComputerDetails.Reachability.LOCAL);
parent.startActivity(intent);
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
return intent;
}
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
ComputerManagerService.ComputerManagerBinder managerBinder) {
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
}
public static void doQuit(final Activity parent,
@@ -0,0 +1,138 @@
package com.limelight.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import com.limelight.AppView;
import com.limelight.AppViewShortcutTrampoline;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ShortcutHelper {
private final ShortcutManager sm;
private final Context context;
public ShortcutHelper(Context context) {
this.context = context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
sm = context.getSystemService(ShortcutManager.class);
}
else {
sm = null;
}
}
@TargetApi(Build.VERSION_CODES.N_MR1)
private void reapShortcutsForDynamicAdd() {
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
for (ShortcutInfo scut : dynamicShortcuts) {
if (maxRankShortcut.getRank() < scut.getRank()) {
maxRankShortcut = scut;
}
}
sm.removeDynamicShortcuts(Collections.singletonList(maxRankShortcut.getId()));
}
}
@TargetApi(Build.VERSION_CODES.N_MR1)
private List<ShortcutInfo> getAllShortcuts() {
LinkedList<ShortcutInfo> list = new LinkedList<>();
list.addAll(sm.getDynamicShortcuts());
list.addAll(sm.getPinnedShortcuts());
return list;
}
@TargetApi(Build.VERSION_CODES.N_MR1)
private ShortcutInfo getInfoForId(String id) {
List<ShortcutInfo> shortcuts = getAllShortcuts();
for (ShortcutInfo info : shortcuts) {
if (info.getId().equals(id)) {
return info;
}
}
return null;
}
@TargetApi(Build.VERSION_CODES.N_MR1)
private boolean isExistingDynamicShortcut(String id) {
for (ShortcutInfo si : sm.getDynamicShortcuts()) {
if (si.getId().equals(id)) {
return true;
}
}
return false;
}
public void reportShortcutUsed(String id) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutInfo sinfo = getInfoForId(id);
if (sinfo != null) {
sm.reportShortcutUsed(id);
}
}
}
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
Intent i = new Intent(context, AppViewShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.setAction(Intent.ACTION_DEFAULT);
ShortcutInfo sinfo = new ShortcutInfo.Builder(context, id)
.setIntent(i)
.setShortLabel(computerName)
.setLongLabel(computerName)
.setIcon(Icon.createWithResource(context, R.mipmap.ic_pc_scut))
.build();
ShortcutInfo existingSinfo = getInfoForId(id);
if (existingSinfo != null) {
// Update in place
sm.updateShortcuts(Collections.singletonList(sinfo));
sm.enableShortcuts(Collections.singletonList(id));
}
// Reap shortcuts to make space for this if it's new
// NOTE: This CAN'T be an else on the above if, because it's
// possible that we have an existing shortcut but it's not a dynamic one.
if (!isExistingDynamicShortcut(id)) {
// To avoid a random carousel of shortcuts popping in and out based on polling status,
// we only add shortcuts if it's not at the limit or the user made a conscious action
// to interact with this PC.
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
reapShortcutsForDynamicAdd();
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
}
}
}
}
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
}
public void disableShortcut(String id, CharSequence reason) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutInfo sinfo = getInfoForId(id);
if (sinfo != null) {
sm.disableShortcuts(Collections.singletonList(id), reason);
}
}
}
}
@@ -15,7 +15,7 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
private ProgressDialog progress;
private final boolean finish;
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<>();
private SpinnerDialog(Activity activity, String title, String message, boolean finish)
{
@@ -15,6 +15,8 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <android/log.h>
#define EVDEV_MAX_EVENT_SIZE 24
@@ -19,7 +19,8 @@ Java_com_limelight_nvstream_av_audio_OpusDecoder_init(JNIEnv *env, jobject this,
ChannelCount = channelCount;
jni_mapping_data = (*env)->GetByteArrayElements(env, mapping, 0);
ret = nv_opus_init(sampleRate, channelCount, streams, coupledStreams, jni_mapping_data);
ret = nv_opus_init(sampleRate, channelCount, streams, coupledStreams,
(const unsigned char*)jni_mapping_data);
(*env)->ReleaseByteArrayElements(env, mapping, jni_mapping_data, JNI_ABORT);
return ret;
@@ -49,7 +50,8 @@ Java_com_limelight_nvstream_av_audio_OpusDecoder_decode(
if (indata != NULL) {
jni_input_data = (*env)->GetByteArrayElements(env, indata, 0);
ret = nv_opus_decode(&jni_input_data[inoff], inlen, (jshort*)jni_pcm_data, SamplesPerChannel);
ret = nv_opus_decode((unsigned char*)&jni_input_data[inoff], inlen,
(jshort*)jni_pcm_data, SamplesPerChannel);
// The input data isn't changed so it can be safely aborted
(*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"
android:fillColor="#FFFFFF"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"
android:fillColor="#FFFFFF"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"
android:fillColor="#FFFFFF"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
android:fillColor="#FFFFFF"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
android:fillColor="#FFFFFF"/>
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

@@ -9,41 +9,40 @@
tools:context=".PcView" >
<RelativeLayout
android:id="@+id/no_pc_found_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">
<ImageView
android:id="@+id/pcs_loading"
android:layout_width="75dp"
android:layout_height="75dp"
android:src="@drawable/image_loading"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/pcs_loading"
android:layout_toEndOf="@+id/pcs_loading"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="@string/searching_pc"/>
</RelativeLayout>
<FrameLayout
android:id="@+id/pcFragmentContainer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/manuallyAddPc"
android:layout_toStartOf="@+id/manuallyAddPc"
android:layout_toRightOf="@+id/settingsButton"
android:layout_toEndOf="@+id/settingsButton"/>
android:layout_toEndOf="@+id/settingsButton">
<RelativeLayout
android:id="@+id/no_pc_found_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">
<ProgressBar
android:id="@+id/pcs_loading"
android:layout_width="75dp"
android:layout_height="75dp"
android:indeterminate="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/pcs_loading"
android:layout_toEndOf="@+id/pcs_loading"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="@string/searching_pc"/>
</RelativeLayout>
</RelativeLayout>
<ImageButton
android:id="@+id/settingsButton"
@@ -51,11 +50,22 @@
android:layout_height="65dp"
android:cropToPadding="false"
android:scaleType="fitXY"
android:nextFocusDown="@+id/pcGridView"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="@drawable/settings"
android:src="@drawable/ic_settings"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
android:id="@+id/helpButton"
android:layout_width="70dp"
android:layout_height="65dp"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_below="@id/settingsButton"
android:src="@drawable/ic_help"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
@@ -64,11 +74,10 @@
android:layout_height="65dp"
android:cropToPadding="false"
android:scaleType="fitXY"
android:nextFocusDown="@+id/pcGridView"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:src="@drawable/add_computer"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_below="@id/helpButton"
android:src="@drawable/ic_add"
style="?android:attr/borderlessButtonStyle"/>
</RelativeLayout>
@@ -9,41 +9,44 @@
tools:context=".PcView" >
<RelativeLayout
android:id="@+id/no_pc_found_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">
<ImageView
android:id="@+id/pcs_loading"
android:layout_width="75dp"
android:layout_height="75dp"
android:src="@drawable/image_loading"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/pcs_loading"
android:layout_toEndOf="@+id/pcs_loading"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="@string/searching_pc"/>
</RelativeLayout>
<FrameLayout
android:id="@+id/pcFragmentContainer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_toLeftOf="@+id/manuallyAddPc"
android:layout_toStartOf="@+id/manuallyAddPc"
android:layout_toRightOf="@+id/settingsButton"
android:layout_toEndOf="@+id/settingsButton"
android:layout_below="@+id/settingsButton"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"/>
>
<RelativeLayout
android:id="@+id/no_pc_found_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">
<ProgressBar
android:id="@+id/pcs_loading"
android:layout_width="75dp"
android:layout_height="75dp"
android:indeterminate="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/pcs_loading"
android:layout_toEndOf="@+id/pcs_loading"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="@string/searching_pc"/>
</RelativeLayout>
</RelativeLayout>
<ImageButton
android:id="@+id/settingsButton"
@@ -54,7 +57,19 @@
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:src="@drawable/settings"
android:src="@drawable/ic_settings"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
android:id="@+id/helpButton"
android:layout_width="70dp"
android:layout_height="65dp"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/settingsButton"
android:layout_toEndOf="@+id/settingsButton"
android:src="@drawable/ic_help"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
@@ -66,7 +81,7 @@
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:src="@drawable/add_computer"
android:src="@drawable/ic_add"
style="?android:attr/borderlessButtonStyle"/>
</RelativeLayout>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_help"
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="com.limelight.HelpActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
@@ -8,6 +8,14 @@
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="75dp"
android:layout_height="75dp"
android:indeterminate="true">
</ProgressBar>
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
@@ -8,6 +8,14 @@
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminate="true">
</ProgressBar>
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
+13 -8
View File
@@ -10,23 +10,28 @@
<ImageView
android:id="@+id/grid_image"
android:layout_centerHorizontal="true"
android:layout_width="150dp"
android:layout_height="100dp">
android:layout_width="125dp"
android:layout_height="125dp">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_marginTop="15dp"
android:layout_marginLeft="65dp"
android:layout_marginStart="65dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:layout_width="50dp"
android:layout_height="50dp">
</ImageView>
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminate="true">
</ProgressBar>
</RelativeLayout>
<TextView
android:id="@+id/grid_text"
android:layout_width="150dp"
android:layout_width="125dp"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
+13 -8
View File
@@ -10,23 +10,28 @@
<ImageView
android:id="@+id/grid_image"
android:layout_centerHorizontal="true"
android:layout_width="100dp"
android:layout_height="67dp">
android:layout_width="75dp"
android:layout_height="75dp">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_marginTop="10dp"
android:layout_marginLeft="42dp"
android:layout_marginStart="42dp"
android:layout_marginRight="13dp"
android:layout_marginEnd="13dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:layout_width="33dp"
android:layout_height="33dp"
android:indeterminate="true">
</ProgressBar>
</RelativeLayout>
<TextView
android:id="@+id/grid_text"
android:layout_width="100dp"
android:layout_width="75dp"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

-9
View File
@@ -1,14 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="resolution_names">
<item>720p - 30 FPS</item>
<item>720p - 60 FPS</item>
<item>1080p - 30 FPS</item>
<item>1080p - 60 FPS</item>
<item>4K - 30 FPS</item>
<item>4K - 60 FPS</item>
</string-array>
<string-array name="decoder_names">
<item>Scegli decoder automaticamente</item>
<item>Forza decoder software</item>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="decoder_names">
<item>デコーダを自動選択</item>
<item>ソフトウェアデコードを強制</item>
<item>ハードウェアデコードを強制</item>
</string-array>
<string-array name="video_format_names">
<item>安定している場合にH.265を有効化</item>
<item>H.265を強制的に有効化(クラッシュする可能性があります)</item>
<item>H.265を無効化</item>
</string-array>
</resources>
+127
View File
@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">ゲームリストを表示</string>
<string name="pcview_menu_pair_pc">コンピュータとペアリング</string>
<string name="pcview_menu_unpair_pc">ペアリングを解除r</string>
<string name="pcview_menu_send_wol">Wake-On-LANリクエストを送信する</string>
<string name="pcview_menu_delete_pc">コンピュータをリストから削除する</string>
<!-- Pair messages -->
<string name="pairing">ペアリング中</string>
<string name="pair_pc_offline">コンピュータはオフラインです</string>
<string name="pair_pc_ingame">現在このコンピュータはゲームを実行中のため、ペアリングする前にゲームを閉じる必要があります</string>
<string name="pair_pairing_title">ペアリング</string>
<string name="pair_pairing_msg">ペアリングしたいコンピュータのPINコードを入力してください:</string>
<string name="pair_incorrect_pin">PINコードが間違っています</string>
<string name="pair_fail">ペアリングに失敗しました</string>
<!-- WOL messages -->
<string name="wol_pc_online">コンピュータはオンラインです</string>
<string name="wol_no_mac">GFEがMACアドレスを送信しなかったため、コンピュータを起動することができません</string>
<string name="wol_waking_pc">コンピュータを起動中</string>
<string name="wol_waking_msg">コンピュータが起動するまで数秒かかる可能性があります。Wake-On-LANが正しく構成されているか確認してください</string>
<string name="wol_fail">Wake-On-LANパケットの送信に失敗しました</string>
<!-- Unpair messages -->
<string name="unpairing">ペアリングの解除中</string>
<string name="unpair_success">ペアリングの解除に成功しました</string>
<string name="unpair_fail">ペアリングの解除に失敗しました</string>
<string name="unpair_error">このデバイスはペアリングされませんでした</string>
<!-- Errors -->
<string name="error_pc_offline">コンピュータはオフラインです</string>
<string name="error_manager_not_running">ComputerManagerサービスが動作していません。しばらく待つか、Moonlightを再起動してください</string>
<string name="error_unknown_host">ホストが解決できません</string>
<string name="error_404">GFEがHTTP 404を返しました。コンピュータでサポートされているGPUが動作しているか確認してください。リモートデスクトップソフトウェアでもこのエラーが発生する可能性があります。コンピュータを再起動するか、GFEを再インストールしてください</string>
<!-- Start application messages -->
<string name="conn_establishing_title">接続を確立</string>
<string name="conn_establishing_msg">接続を開始</string>
<string name="conn_metered">警告: 現在使用中のネットワークは従量制課金接続です</string>
<string name="conn_client_latency"> デコードの平均待ち時間:</string>
<string name="conn_client_latency_hw">ハードウェアデコーダーの待ち時間</string>
<string name="conn_hardware_latency">ハードウェアデコーダーの平均待ち時間:</string>
<string name="conn_starting">開始</string>
<string name="conn_error_title">接続エラー</string>
<string name="conn_error_msg">開始できません</string>
<string name="conn_terminated_title">接続を終了</string>
<string name="conn_terminated_msg">接続は終了しました</string>
<!-- General strings -->
<string name="ip_hint">コンピュータのIPアドレス</string>
<string name="searching_pc">コンピュータを検索中</string>
<string name="yes">はい</string>
<string name="no">いいえ</string>
<string name="lost_connection">コンピュータとの接続が失われました</string>
<!-- AppList activity -->
<string name="title_applist">ゲーム</string>
<string name="applist_menu_resume">セッションを続ける</string>
<string name="applist_menu_quit">セッションを終了する</string>
<string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string>
<string name="applist_menu_cancel">キャンセル</string>
<string name="applist_refresh_title">ゲームリスト</string>
<string name="applist_refresh_msg">ゲームリストを更新中</string>
<string name="applist_refresh_error_title">エラー</string>
<string name="applist_refresh_error_msg">ゲームリストの取得に失敗しました</string>
<string name="applist_quit_app">終了中</string>
<string name="applist_quit_success">ゲームを終了しました</string>
<string name="applist_quit_fail">ゲームが終了できませんでした</string>
<string name="applist_quit_confirmation">本当にゲームを終了しますか? 保存されていないデータは破棄されます</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">手動でコンピュータを追加する</string>
<string name="msg_add_pc">コンピュータに接続中</string>
<string name="addpc_fail">指定されたコンピュータに接続できませんでした。ファイアーウォールの設定を確認してください</string>
<string name="addpc_success">コンピュータの追加に成功しました</string>
<string name="addpc_unknown_host">コンピュータのアドレスが解決できません。コンピュータのアドレスを確認してください</string>
<string name="addpc_enter_ip">IPアドレスを入力してください</string>
<!-- Preferences -->
<string name="category_basic_settings">基本的な設定</string>
<string name="title_resolution_list">解像度とフレームレート</string>
<string name="summary_resolution_list">品質が高いほどラグとクラッシュが発生しやすくなります</string>
<string name="title_seekbar_bitrate">映像のビットレート</string>
<string name="summary_seekbar_bitrate">ビットレートを低くすればカクつきが抑制され、高くすれば画質が向上します</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="title_checkbox_stretch_video">映像を全画面に拡大</string>
<string name="title_checkbox_disable_warnings">警告を無効化</string>
<string name="summary_checkbox_disable_warnings">ストリーミング中に画面に警告メッセージを表示しない</string>
<string name="category_audio_settings">音声</string>
<string name="title_checkbox_51_surround">5.1chサラウンド</string>
<string name="summary_checkbox_51_surround">音声に問題が生じる場合はチェックを外してください。バージョン2.7以降のGFEが必要です</string>
<string name="category_gamepad_settings">ゲームコントローラ</string>
<string name="title_checkbox_multi_controller">複数のゲームコントローラ</string>
<string name="summary_checkbox_multi_controller">チェックを外すと、全てのゲームコントローラが単一の物として認識されます</string>
<string name="title_seekbar_deadzone">アナログゲームコントローラのデッドゾーン</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/Oneコントローラ</string>
<string name="summary_checkbox_xb1_driver">XboxゲームコントローラをサポートしないデバイスでXboxゲームコントローラ用のドライバを有効にします</string>
<string name="category_on_screen_controls_settings">オンスクリーンコントローラ</string>
<string name="title_checkbox_show_onscreen_controls">オンスクリーンコントローラ</string>
<string name="summary_checkbox_show_onscreen_controls">タッチスクリーン上にオンスクリーンコントローラを表示します</string>
<string name="category_ui_settings">インターフェース</string>
<string name="title_language_list">言語</string>
<string name="summary_language_list">Moonlightで使用する言語</string>
<string name="title_checkbox_list_mode">リストメニュー</string>
<string name="summary_checkbox_list_mode">ゲームをグリッドではなく、リストで表示します</string>
<string name="title_checkbox_small_icon_mode">小さなアイコン</string>
<string name="summary_checkbox_small_icon_mode">グリッドで小さなアイコンを使用します</string>
<string name="category_host_settings">ホスト</string>
<string name="title_checkbox_enable_sops">ゲーム設定の最適化</string>
<string name="summary_checkbox_enable_sops">GFEがゲームの設定を最適化します</string>
<string name="title_checkbox_host_audio">コンピュータで音声を再生</string>
<string name="summary_checkbox_host_audio">音声をコンピュータとこのデバイスの両方で再生します</string>
<string name="category_advanced_settings">高度な設定</string>
<string name="title_video_format">H.265</string>
<string name="summary_video_format">H.265は動画に必要な帯域幅を圧縮します。この機能にはなるべく新しいデバイスが必要です</string>
</resources>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="decoder_names">
<item>Selecteer Decoder Automatisch</item>
<item>Forceer Software Decoderen</item>
<item>Forceer Hardware Decoderen</item>
</string-array>
</resources>
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Toon Spel Lijst</string>
<string name="pcview_menu_pair_pc">Koppel met PC</string>
<string name="pcview_menu_unpair_pc">Ontkoppen</string>
<string name="pcview_menu_send_wol">Stuur Wake-On-LAN aanvraag</string>
<string name="pcview_menu_delete_pc">Verwijder PC</string>
<!-- Pair messages -->
<string name="pairing">Koppelen…</string>
<string name="pair_pc_offline">Computer is onbereikbaar</string>
<string name="pair_pc_ingame">Computer is op dit moment in een spel. Sluit de spel voordat je koppelt.</string>
<string name="pair_pairing_title">Koppelen</string>
<string name="pair_pairing_msg">Voer de volgende PIN in op de doel PC:</string>
<string name="pair_incorrect_pin">Ongeldige PIN</string>
<string name="pair_fail">Koppelen mislukt</string>
<!-- WOL messages -->
<string name="wol_pc_online">Computer is online</string>
<string name="wol_no_mac">Het is niet mogelijk om de PC uit slaapstand te halen, omdat GFE geen MAC adres heeft verstuurd.</string>
<string name="wol_waking_pc">PC uit slaapstand halen…</string>
<string name="wol_waking_msg"> Het ken even duren voordat de PC reageert.
Als dit niet gebeurt dan moet je de Wake-On-LAN instellingen controleren.
</string>
<string name="wol_fail">Wake-On-LAN packets versturen mislukt</string>
<!-- Unpair messages -->
<string name="unpairing">Ontkoppelen…</string>
<string name="unpair_success">Ontkoppeling succesvol</string>
<string name="unpair_fail">Ontkoppeling mislukt</string>
<string name="unpair_error">Apparaat niet gekoppeld</string>
<!-- Errors -->
<string name="error_pc_offline">Computer is offline</string>
<string name="error_manager_not_running">The ComputerManager service is niet gestart. Wacht een ogenblik of herstart de app.</string>
<string name="error_unknown_host">Host achterhalen mislukt.</string>
<string name="error_404">GFE geeft een HTTP 404 fout. Kijk na of de PC een ondersteunde GPU heeft.
Remote desktop software kan soms ook voor deze fout zorgen. Probeer de computer opnieuw op te starten of GFE te herinstalleren.
</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Verbinding maken</string>
<string name="conn_establishing_msg">Verbinding starten</string>
<string name="conn_metered">Waarschuwing: Dit internet verbinding bevat een datalimiet!</string>
<string name="conn_client_latency">Gemiddelde frame decoding reactietijd:</string>
<string name="conn_client_latency_hw">hardware decoder reactietijd:</string>
<string name="conn_hardware_latency">Gemiddelde hardware decoding reactietijd:</string>
<string name="conn_starting">Starten</string>
<string name="conn_error_title">Verbindingsprobleem</string>
<string name="conn_error_msg">Starten mislukt</string>
<string name="conn_terminated_title">Verbinding beëindigd</string>
<string name="conn_terminated_msg">De verbinding is beëindigd</string>
<!-- General strings -->
<string name="ip_hint">IP adres van GeForce PC</string>
<string name="searching_pc">Zoeken naar PCs…</string>
<string name="yes">Ja</string>
<string name="no">Nee</string>
<string name="lost_connection">Verbinding met PC verloren</string>
<!-- AppList activity -->
<string name="title_applist">Apps op</string>
<string name="applist_menu_resume">Hervat Sessie</string>
<string name="applist_menu_quit">Stop Sessie</string>
<string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string>
<string name="applist_menu_cancel">Annuleer</string>
<string name="applist_refresh_title">App Lijst</string>
<string name="applist_refresh_msg">Apps Verversen…</string>
<string name="applist_refresh_error_title">Fout</string>
<string name="applist_refresh_error_msg">App lijst ophalen mislukt</string>
<string name="applist_quit_app">Stoppen</string>
<string name="applist_quit_success">Succesvol gestopt.</string>
<string name="applist_quit_fail">Stoppen mislukt</string>
<string name="applist_quit_confirmation">Weet je het zeker dat je de app wilt sluiten? Alle niet-opgeslagen gegevens gaan verloren.</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Voeg handmatig PC toe</string>
<string name="msg_add_pc">Verbinding maken met PC…</string>
<string name="addpc_fail">Verbinden mislukt. Controleer of de benodigde poorten openstaan in de firewall.</string>
<string name="addpc_success">Computer toevoegen succesvol</string>
<string name="addpc_unknown_host">Adres achterhalen is mislukt. Controleer het ingevoerde adres op typfouten.</string>
<string name="addpc_enter_ip">IP-adres invoeren is verplicht.</string>
<!-- Preferences -->
<string name="category_basic_settings">Algemene Instellingen</string>
<string name="title_resolution_list">Selecteer resolutie en FPS doel</string>
<string name="summary_resolution_list">Instellingen te hoog zetten kunnen crashes en haperingen veroorzaken.</string>
<string name="title_seekbar_bitrate">Selecteer doel video bitsnelheid</string>
<string name="summary_seekbar_bitrate">Verlaag bitsnelheid om haperingen te verminderen. Verhoog de bitsnelheid voor een betere videokwaliteit.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="title_checkbox_stretch_video">Rek video uit tot volledig scherm</string>
<string name="title_checkbox_disable_warnings">Verberg waarschuwingsberichten</string>
<string name="summary_checkbox_disable_warnings">Verberg on-screen verbindingswaarschuwingen tijdens het streamen</string>
<string name="category_gamepad_settings">Gamepad Instellingen</string>
<string name="title_checkbox_multi_controller">Multi-gamepad support</string>
<string name="summary_checkbox_multi_controller">Wanneer uitgevinkt, alle controllers verschijnen als één.</string>
<string name="title_seekbar_deadzone">Pas analoge dodezone aan.</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox One controller stuurprogramma</string>
<string name="summary_checkbox_xb1_driver">Gebruikt de ingebouwde USB stuurprogramma voor apparaten zonder Xbox One controller ondersteuning.</string>
<string name="category_ui_settings">UI Installingen</string>
<string name="title_language_list">Taal</string>
<string name="summary_language_list">Taal te gebruiken in Moonlight</string>
<string name="title_checkbox_list_mode">Gebruik lijsten in plaats van kolommen</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="title_checkbox_small_icon_mode">Gebruik kleine iconen</string>
<string name="summary_checkbox_small_icon_mode">Gebruik kleine iconen in kolom onderdelen zodat meer items tegelijk zichtbaar worden.</string>
<string name="category_host_settings">Host Instellingen</string>
<string name="title_checkbox_enable_sops">Optimaliseer spel instellingen</string>
<string name="summary_checkbox_enable_sops">Sta GFE toe om spel instellingen te veranderen voor een optimale stream</string>
<string name="title_checkbox_host_audio">Speel audio af op PC</string>
<string name="summary_checkbox_host_audio">Speel audio af op de computer en op dit apparaat</string>
<string name="category_advanced_settings">Geavanceerde Instellingen</string>
</resources>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="decoder_names">
<item>Автоматический выбор декодера</item>
<item>Принудительное программное декодирование</item>
<item>Принудительное аппаратное декодирование</item>
</string-array>
<string-array name="video_format_names">
<item>Использовать H.265 только если безопасно</item>
<item>Всегда использовать H.265 если доступно</item>
<item>Никогда не использовать H.265</item>
</string-array>
</resources>
+127
View File
@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Посмотреть список игр</string>
<string name="pcview_menu_pair_pc">Создать пару с PC</string>
<string name="pcview_menu_unpair_pc">Разорвать пару</string>
<string name="pcview_menu_send_wol">Отправить Wake-On-LAN запрос</string>
<string name="pcview_menu_delete_pc">Удалить PC</string>
<!-- Pair messages -->
<string name="pairing">Создание пары…</string>
<string name="pair_pc_offline">Компьютер выключен или находится не в сети</string>
<string name="pair_pc_ingame">Компьютер в данный момент находится в игре. Вы должны закрыть игру перед создание пары.</string>
<string name="pair_pairing_title">Создание пары</string>
<string name="pair_pairing_msg">Пожалуйста введите этот PIN на PC:</string>
<string name="pair_incorrect_pin">Неправильный PIN</string>
<string name="pair_fail">Создание пары не удалось</string>
<!-- WOL messages -->
<string name="wol_pc_online">Компьютер в сети</string>
<string name="wol_no_mac">Невозможно разбудить PC потому что GFE не отправило MAC адрес</string>
<string name="wol_waking_pc">Пробуждение PC…</string>
<string name="wol_waking_msg">Пробуждение PC может занять несколько секунд.
Если этого не происходит, удостоверьтесь что Wake-On-LAN настроен правильно.
</string>
<string name="wol_fail">Ошибка при отправке Wake-On-LAN пакетов</string>
<!-- Unpair messages -->
<string name="unpairing">Разрыв пары…</string>
<string name="unpair_success">Разрыв пары закончился успешно.</string>
<string name="unpair_fail">Разрыв пары не удался</string>
<string name="unpair_error">Устройство не было спарено</string>
<!-- Errors -->
<string name="error_pc_offline">Компьютер выключен или находится не в сети</string>
<string name="error_manager_not_running">Сервис ComputerManager не запущен. Пожалуйста, подождите несколько секунд или перезапустите приложение.</string>
<string name="error_unknown_host">Не удалось найти хост</string>
<string name="error_404">GFE вернул ошибку HTTP 404. Убедитесь что ваш PC is испольщует поддерживаемый GPU.
Использование программ для удалённого доступа также можнт вызывать эту ошибку. Попробуйте перезагрузить компьютер или переустановить GFE.
</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Создание соединения.</string>
<string name="conn_establishing_msg">Подключение</string>
<string name="conn_metered">Внимание: Происходит измерение вашего сетевого соединения!</string>
<string name="conn_client_latency">Средняя задержка декодирования кадра: </string>
<string name="conn_client_latency_hw">задержка аппаратного декодирования:</string>
<string name="conn_hardware_latency">Средняя задержка апаратного декодирования:</string>
<string name="conn_starting">Запуск</string>
<string name="conn_error_title">Ошибка соединения</string>
<string name="conn_error_msg">Запуск не удался</string>
<string name="conn_terminated_title">Соединение прекращено</string>
<string name="conn_terminated_msg">Подключение было прервано</string>
<!-- General strings -->
<string name="ip_hint">IP адресс компьютера с GeForce</string>
<string name="searching_pc">Поиск компьютеров…</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="lost_connection">Потеряно соединение с PC</string>
<!-- AppList activity -->
<string name="title_applist">Приложения на</string>
<string name="applist_menu_resume">Возобновить сессию</string>
<string name="applist_menu_quit">Выйти из сессии</string>
<string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string>
<string name="applist_menu_cancel">Отмена</string>
<string name="applist_refresh_title">Список приложений</string>
<string name="applist_refresh_msg">Обновление приложений…</string>
<string name="applist_refresh_error_title">Ошибка</string>
<string name="applist_refresh_error_msg">Ошибка при получении списка приложений</string>
<string name="applist_quit_app">Выход из</string>
<string name="applist_quit_success">Выход произошёл успешно из</string>
<string name="applist_quit_fail">Ошибка при выходе</string>
<string name="applist_quit_confirmation">Вы уверены, что хотите выйти из запущенного приложения? Все несохраненные данные будут потеряны.</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Добавление PC вручную</string>
<string name="msg_add_pc">Соединение с PC…</string>
<string name="addpc_fail">Не удалось подключиться к выбранному компьютеру. Удостоверьтесь, что необходимые порты разрешены в настройках брандмауэра.</string>
<string name="addpc_success">Компьютер добавлен успешно.</string>
<string name="addpc_unknown_host">Не удалось найти PC по указанному адресу. Убедитесь, что вы не совершили ошибок во время его написания.</string>
<string name="addpc_enter_ip">Вы должны ввести IP адрес</string>
<!-- Preferences -->
<string name="category_basic_settings">Базовые Настройки</string>
<string name="title_resolution_list">Выберите разрешение и частоту кадров.</string>
<string name="summary_resolution_list">Выбор слишком высокого значеня для своего устройства может вызвать тормоза или вылеты.</string>
<string name="title_seekbar_bitrate">Выберите битрейт видео.</string>
<string name="summary_seekbar_bitrate">Низкий битрейт уменьшит зависания. Увеличение битрейта улучшит качество изображения.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="title_checkbox_stretch_video">Растягивать видео на весь экран</string>
<string name="title_checkbox_disable_warnings">Отключить сообщения с предупреждениями</string>
<string name="summary_checkbox_disable_warnings">Выключить экранные предупреждения о соединении во время стрима.</string>
<string name="category_audio_settings">Аудио Настройки</string>
<string name="title_checkbox_51_surround">Включить объёмный звук 5.1</string>
<string name="summary_checkbox_51_surround">Отключите, если появляются аудио проблемы. Требуется GFE 2.7 или выше.</string>
<string name="category_gamepad_settings">Настройки Гемпада</string>
<string name="title_checkbox_multi_controller">Поддержка нескольких контроллеров</string>
<string name="summary_checkbox_multi_controller">Когда отключена, все контроллеры определяются как один. </string>
<string name="title_seekbar_deadzone">Регулировать мертвую зону аналогового стика.</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контроллера от Xbox One</string>
<string name="summary_checkbox_xb1_driver">Включить встроенный USB драйвер для устройств без встроенной поддержки контроллера от Xbox One.</string>
<string name="category_ui_settings">Настройки Интерфейса</string>
<string name="title_language_list">Язык</string>
<string name="summary_language_list">Язык, который будет использоваться в Moonlight</string>
<string name="title_checkbox_list_mode">Использовать списки вместо сеток.</string>
<string name="summary_checkbox_list_mode">Выводить приложения и компьютеры списком, вместо использования сетки.</string>
<string name="title_checkbox_small_icon_mode">Использовать маленькие иконки</string>
<string name="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетки для увеличения числа элементов, отображаемых на экране.</string>
<string name="category_host_settings">Настройки Хоста</string>
<string name="title_checkbox_enable_sops">Оптимизировать игровые настройки</string>
<string name="summary_checkbox_enable_sops">Разрешить GFE изменять настройки игр для оптимальной потоковой передачи</string>
<string name="title_checkbox_host_audio">Проигрывать звук на PC</string>
<string name="summary_checkbox_host_audio">Проигрывать звук на компьютере и текущем устройстве.</string>
<string name="category_advanced_settings">Расширенные Настройки</string>
<string name="title_video_format">Изменить настройки H.265</string>
<string name="summary_video_format">H.265 снижает требования к пропускной способности, но требует очень свежих устройств.</string>
</resources>
+8 -2
View File
@@ -21,11 +21,17 @@
<item>Default</item>
<item>English</item>
<item>Italiano</item>
<item>Japanese</item>
<item>Русский</item>
<item>Nederlands</item>
</string-array>
<string-array name="language_values" translatable="false">
<item>default</item>
<item>en</item>
<item>it</item>
<item>ja</item>
<item>ru</item>
<item>nl</item>
</string-array>
<string-array name="decoder_names">
@@ -40,8 +46,8 @@
</string-array>
<string-array name="video_format_names">
<item>Use H.265 only if safe</item>
<item>Always use H.265 if available</item>
<item>Use H.265 only if stable</item>
<item>Always use H.265 (may crash)</item>
<item>Never use H.265</item>
</string-array>
<string-array name="video_format_values" translatable="false">
+12 -1
View File
@@ -1,5 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC deleted</string>
<string name="scut_not_paired">PC not paired</string>
<!-- Help strings -->
<string name="help_loading_title">Help Viewer</string>
<string name="help_loading_msg">Loading help page…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">View Game List</string>
@@ -16,6 +23,7 @@
<string name="pair_pairing_msg">Please enter the following PIN on the target PC:</string>
<string name="pair_incorrect_pin">Incorrect PIN</string>
<string name="pair_fail">Pairing failed</string>
<string name="pair_already_in_progress">Pairing already in progress</string>
<!-- WOL messages -->
<string name="wol_pc_online">Computer is online</string>
@@ -55,13 +63,16 @@
<!-- General strings -->
<string name="ip_hint">IP address of GeForce PC</string>
<string name="searching_pc">Searching for PCs</string>
<string name="searching_pc">Searching for PCs with GameStream running…\n\n
Ensure GameStream is enabled in the GeForce Experience SHIELD settings.</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="lost_connection">Lost connection to PC</string>
<string name="help">Help</string>
<!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg">Connecting to PC…</string>
<string name="applist_menu_resume">Resume Session</string>
<string name="applist_menu_quit">Quit Session</string>
<string name="applist_menu_quit_and_start">Quit Current Game and Start</string>
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.android.tools.build:gradle:2.2.2'
}
}
+19
View File
@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx3072m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+2 -2
View File
@@ -1,6 +1,6 @@
#Sat Feb 06 16:21:20 EST 2016
#Fri Aug 26 22:01:33 PDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+1 -1
View File
@@ -13,7 +13,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>