mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-20 15:27:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b16676b54a | ||
|
|
dc9bfe5189 | ||
|
|
20039a422e | ||
|
|
22b9c9ca68 | ||
|
|
0c546e35ec | ||
|
|
b70370ac09 | ||
|
|
aa10bb7dc5 | ||
|
|
c6100a9be1 | ||
|
|
529a2f7bf8 | ||
|
|
9ec7e916c5 | ||
|
|
982b36cf98 | ||
|
|
a73eab5e92 | ||
|
|
a8479ccb5f | ||
|
|
f55e4e0e01 | ||
|
|
d08c32ce04 | ||
|
|
1d599c5e60 | ||
|
|
e888ae59e4 | ||
|
|
951d544894 | ||
|
|
49898b34e1 | ||
|
|
3854a6a42e | ||
|
|
d4da5bc281 | ||
|
|
04954f5242 | ||
|
|
9fc5496526 | ||
|
|
e363d24b1c | ||
|
|
801f4027a2 | ||
|
|
c0dc344f76 | ||
|
|
b5b3d81f00 | ||
|
|
8dd8dbc1d1 | ||
|
|
8f31aa59a8 | ||
|
|
5b581b6c0f | ||
|
|
297ac64fde | ||
|
|
d4490f0e17 | ||
|
|
d04e7a3231 | ||
|
|
5b456aba27 | ||
|
|
0c065dcc1f | ||
|
|
531f73329d | ||
|
|
d6ba72032d | ||
|
|
bfdc7a2609 | ||
|
|
031abf03da | ||
|
|
6aac8e6be6 | ||
|
|
8ff93d21c3 | ||
|
|
6df3d0bc44 | ||
|
|
0b18e8fdb4 | ||
|
|
19d8ae0f78 | ||
|
|
d7ffb5dddc | ||
|
|
2859b73dfe | ||
|
|
6f9021a5e6 | ||
|
|
3bfeaefdbd | ||
|
|
db1eace975 | ||
|
|
cab0fa176e | ||
|
|
2f9ae107a2 | ||
|
|
18c93abcb3 | ||
|
|
bd64dfb661 | ||
|
|
82619063ee | ||
|
|
5dbf18d66e | ||
|
|
6a34ff2728 | ||
|
|
f7c7487756 | ||
|
|
f966cb4ca0 | ||
|
|
549563a3d2 | ||
|
|
c5f2a3f8fe | ||
|
|
81a3bbd5e8 | ||
|
|
1509a2a799 | ||
|
|
fc547b734f | ||
|
|
b3700b5a19 | ||
|
|
2b29682095 | ||
|
|
286094ee33 | ||
|
|
c7a061d24e | ||
|
|
4bdc2e0aba | ||
|
|
e69061082b | ||
|
|
1da2ec3cb1 | ||
|
|
8ffc3b80b2 | ||
|
|
08f8b6cb8e | ||
|
|
fb09c9692c | ||
|
|
4901b0c78f | ||
|
|
0a2117241f | ||
|
|
f352cfd15b | ||
|
|
ac7c5c1064 | ||
|
|
077cb2103d | ||
|
|
cdeda011a4 | ||
|
|
894c146988 | ||
|
|
61cc9e151f | ||
|
|
cfe4c9ff21 | ||
|
|
d4bd29b320 | ||
|
|
7f2f2056c3 | ||
|
|
4dd3b2cfb7 | ||
|
|
2e62ad0f00 | ||
|
|
41ef292b82 | ||
|
|
aa60671c88 | ||
|
|
f1ccba39e8 | ||
|
|
226e580a30 | ||
|
|
6f8e719200 | ||
|
|
c127af1e05 | ||
|
|
648904cc69 | ||
|
|
dc85ddb3f9 | ||
|
|
23a7d8555f | ||
|
|
bc9e250d34 | ||
|
|
2203186527 | ||
|
|
53d3d9ecb8 | ||
|
|
de549f67a1 | ||
|
|
755c41481a | ||
|
|
aebc2126bc | ||
|
|
f43547fb31 | ||
|
|
398e4df7cf | ||
|
|
ff68efc3f5 | ||
|
|
8ba2f51bda | ||
|
|
87b79b278b | ||
|
|
121e3ea9be | ||
|
|
ec6ed79ee1 | ||
|
|
ca125826a7 | ||
|
|
dd0aecf108 | ||
|
|
ef5cb2f0cd | ||
|
|
e5a7bb40e9 | ||
|
|
bfdda48fee | ||
|
|
ebea1bb5c1 | ||
|
|
14bc1552fc | ||
|
|
a5b80d3944 | ||
|
|
75d0eedc2b | ||
|
|
29ac7028fa | ||
|
|
8a63b61495 | ||
|
|
eb9e6443e2 | ||
|
|
362c466a16 | ||
|
|
5dac42646b | ||
|
|
c25faf6426 | ||
|
|
81df1245b4 | ||
|
|
2bf4d92185 | ||
|
|
ae6073fe80 | ||
|
|
d0463da2a1 | ||
|
|
c0f8001627 | ||
|
|
f39bf61b04 | ||
|
|
9c8237dab0 | ||
|
|
b88251fa79 | ||
|
|
208855917e | ||
|
|
34bdf450e9 | ||
|
|
998fa1f4e9 | ||
|
|
5c80f7d58c | ||
|
|
7552181e24 | ||
|
|
4b2e26050e | ||
|
|
530b48de71 | ||
|
|
f4721901f8 | ||
|
|
8b692269c1 | ||
|
|
079eca7b4d | ||
|
|
fee40cdbe2 | ||
|
|
66920bb4cb | ||
|
|
fdbf810aa2 | ||
|
|
08bfc1de4a | ||
|
|
76149328fe | ||
|
|
285f33f3f1 | ||
|
|
b17c1b7588 | ||
|
|
5b25c90db8 | ||
|
|
931a0a5168 | ||
|
|
f6a46438bd | ||
|
|
4a60ec1755 | ||
|
|
ec222413dd | ||
|
|
5a28239813 | ||
|
|
da45cba2ff | ||
|
|
54bc34496a | ||
|
|
294910ac84 | ||
|
|
71d2c6a5d5 | ||
|
|
79bf17fe24 | ||
|
|
31f66031bc | ||
|
|
d3f2284791 | ||
|
|
ec647608c4 | ||
|
|
597582ddd8 | ||
|
|
c6d9889182 | ||
|
|
7c58234174 | ||
|
|
ae9282b0af | ||
|
|
310ba646fc | ||
|
|
d479908939 | ||
|
|
5cd5d68d22 | ||
|
|
3e0bf25acb | ||
|
|
f3d277c94a | ||
|
|
04545ecbb0 | ||
|
|
5350651d6f | ||
|
|
f2e2e28419 | ||
|
|
b9031785ac | ||
|
|
91a72474a1 | ||
|
|
b6e7c425c6 | ||
|
|
834ace4566 | ||
|
|
54af70005d | ||
|
|
f2bf168925 | ||
|
|
27ffbd8dec | ||
|
|
eaa82592fe | ||
|
|
73784585a8 | ||
|
|
262d562dd9 | ||
|
|
ab4f904dc9 | ||
|
|
fc4fdd5ee2 | ||
|
|
41c5b62b1a | ||
|
|
239cb0435c | ||
|
|
c6ccc7a6e2 | ||
|
|
6cedb9019c | ||
|
|
8bc64f0438 | ||
|
|
89e6e39e58 | ||
|
|
645761f677 | ||
|
|
0fc60f7855 | ||
|
|
ce38460d87 | ||
|
|
de8e759d3a | ||
|
|
06f6134538 | ||
|
|
ac352b3a23 | ||
|
|
9b8e65e552 | ||
|
|
35999a05f0 | ||
|
|
86ee30e9b4 | ||
|
|
a81c4a1e23 | ||
|
|
394ce458a0 | ||
|
|
f187e57899 | ||
|
|
a15335872d | ||
|
|
beb77b4dab | ||
|
|
aa80d8cd0a | ||
|
|
77d197f14e | ||
|
|
f98fbb778c | ||
|
|
c46a0106f2 | ||
|
|
cbf3db0be0 | ||
|
|
21f3710083 | ||
|
|
8ac5768f4f | ||
|
|
2458b9305c | ||
|
|
a8909ea2a5 | ||
|
|
ac7c35c6c2 | ||
|
|
e4631b5a85 | ||
|
|
e1c50b5dc5 | ||
|
|
c6c5a5cd12 | ||
|
|
bd4854a607 | ||
|
|
cd0181e6f4 | ||
|
|
287b1d2b4d | ||
|
|
10c61bb0a7 | ||
|
|
92215ac34f | ||
|
|
f64d50d8c8 | ||
|
|
b74e0ce48f | ||
|
|
27cb0029a8 | ||
|
|
ce6f193f06 | ||
|
|
a862ffdde4 | ||
|
|
3f1cd8a118 | ||
|
|
bb4b5838e3 | ||
|
|
ea98d64184 | ||
|
|
98f3c56da5 | ||
|
|
20b7619380 | ||
|
|
7b1c3f05c7 | ||
|
|
9166998442 | ||
|
|
e1f6b577bf | ||
|
|
ba0d08b2a6 | ||
|
|
e79c12a038 | ||
|
|
2ca5182a28 | ||
|
|
205e627209 | ||
|
|
425d4f3f63 | ||
|
|
d69843e122 | ||
|
|
d2586d3b59 | ||
|
|
edab84c89b | ||
|
|
dd08754f1f | ||
|
|
2cdfe85091 | ||
|
|
a11acef36f | ||
|
|
1e34dbf616 | ||
|
|
b3d4763ef6 | ||
|
|
fe630e9383 | ||
|
|
826a20785f | ||
|
|
75932d7621 | ||
|
|
62d095af4f | ||
|
|
1594735aa0 | ||
|
|
cbd0bdf9fc | ||
|
|
d3e8e8fb9c | ||
|
|
66406c5a48 | ||
|
|
753c600dd2 | ||
|
|
b28b1df348 | ||
|
|
b94649162e | ||
|
|
ee50e19dbd | ||
|
|
cc23f8b831 | ||
|
|
bac7b68bb1 | ||
|
|
f9a622c89b | ||
|
|
c321dc5e81 | ||
|
|
72f37c9df4 | ||
|
|
544eac0c8a | ||
|
|
823593ddae | ||
|
|
3600e704c4 | ||
|
|
0c79d756a4 | ||
|
|
eb531a7a88 | ||
|
|
d6634d30dc | ||
|
|
f87806b1b4 | ||
|
|
2a5afeb5ff | ||
|
|
fc5495f1ec | ||
|
|
699cc361a2 | ||
|
|
31bf4f10c0 | ||
|
|
fe704af62f | ||
|
|
e74517543d | ||
|
|
44acf19742 | ||
|
|
bf20aa253e | ||
|
|
81c815840d | ||
|
|
e9cd63dc5f | ||
|
|
1ae8f67d93 | ||
|
|
daa1e10333 | ||
|
|
a8a356e703 | ||
|
|
ca440cc5dd | ||
|
|
95a9fb4f62 | ||
|
|
7db9e27112 | ||
|
|
03bcdbe3f7 | ||
|
|
f0762a6213 | ||
|
|
67fbc6b3ad | ||
|
|
d9662d7396 | ||
|
|
5ccbbf259d | ||
|
|
179c2f8723 | ||
|
|
c76e0a40a7 | ||
|
|
03407e528f | ||
|
|
0c41d742cf | ||
|
|
ed2f471a4e | ||
|
|
04efec101e | ||
|
|
a6c69012cc | ||
|
|
0045c54d8e | ||
|
|
45436c006f | ||
|
|
cc183c0da8 | ||
|
|
523f1df98b | ||
|
|
5843dff278 | ||
|
|
7f24f47978 | ||
|
|
b1f9fd459e | ||
|
|
48988eb785 |
No files matched your search
@@ -1,15 +1,16 @@
|
|||||||
# Moonlight Android
|
# Moonlight Android
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
||||||
|
[](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
|
||||||
|
|
||||||
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
[Moonlight for Android](https://moonlight-stream.org) is an open source client for NVIDIA GameStream, as used by the NVIDIA Shield.
|
||||||
|
|
||||||
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
|
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
|
||||||
whether in your own home or over the internet.
|
whether in your own home or over the internet.
|
||||||
|
|
||||||
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [iOS/tvOS client](https://github.com/moonlight-stream/moonlight-ios).
|
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [iOS/tvOS client](https://github.com/moonlight-stream/moonlight-ios).
|
||||||
|
|
||||||
Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps.
|
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-android/).
|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
|
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
|
||||||
|
|||||||
+25
-15
@@ -1,23 +1,28 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
ndkVersion "23.2.8568313"
|
||||||
|
|
||||||
|
compileSdk 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdk 16
|
||||||
targetSdkVersion 30
|
targetSdk 33
|
||||||
|
|
||||||
versionName "9.8.3"
|
versionName "10.4"
|
||||||
versionCode = 254
|
versionCode = 280
|
||||||
|
|
||||||
|
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||||
|
ndk.debugSymbolLevel = 'FULL'
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "root"
|
flavorDimensions.add("root")
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
root {
|
root {
|
||||||
// Android O has native mouse capture, so don't show the rooted
|
// Android O has native mouse capture, so don't show the rooted
|
||||||
// version to devices running O on the Play Store.
|
// version to devices running O on the Play Store.
|
||||||
maxSdkVersion 25
|
maxSdk 25
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
@@ -27,6 +32,7 @@ android {
|
|||||||
|
|
||||||
applicationId "com.limelight.root"
|
applicationId "com.limelight.root"
|
||||||
dimension "root"
|
dimension "root"
|
||||||
|
buildConfigField "boolean", "ROOT_BUILD", "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
nonRoot {
|
nonRoot {
|
||||||
@@ -38,12 +44,13 @@ android {
|
|||||||
|
|
||||||
applicationId "com.limelight"
|
applicationId "com.limelight"
|
||||||
dimension "root"
|
dimension "root"
|
||||||
|
buildConfigField "boolean", "ROOT_BUILD", "false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lint {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation'
|
||||||
lintConfig file("lint.xml")
|
lintConfig file('lint.xml')
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle {
|
bundle {
|
||||||
@@ -53,7 +60,7 @@ android {
|
|||||||
enableSplit = false
|
enableSplit = false
|
||||||
}
|
}
|
||||||
density {
|
density {
|
||||||
// FIXME: This should not be neccessary but we get
|
// FIXME: This should not be necessary but we get
|
||||||
// weird crashes due to missing drawable resources
|
// weird crashes due to missing drawable resources
|
||||||
// when this split is enabled.
|
// when this split is enabled.
|
||||||
enableSplit = false
|
enableSplit = false
|
||||||
@@ -63,9 +70,10 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
|
resValue "string", "app_label", "Moonlight (Debug)"
|
||||||
|
resValue "string", "app_label_root", "Moonlight (Root Debug)"
|
||||||
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
useProguard false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
@@ -100,6 +108,8 @@ android {
|
|||||||
//
|
//
|
||||||
// TL;DR: Leave the following line alone!
|
// TL;DR: Leave the following line alone!
|
||||||
applicationIdSuffix ".unofficial"
|
applicationIdSuffix ".unofficial"
|
||||||
|
resValue "string", "app_label", "Moonlight"
|
||||||
|
resValue "string", "app_label_root", "Moonlight (Root)"
|
||||||
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
@@ -114,10 +124,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
|
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||||
implementation 'org.jcodec:jcodec:0.2.3'
|
implementation 'org.jcodec:jcodec:0.2.3'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
||||||
implementation 'com.squareup.okio:okio:1.17.5'
|
implementation 'com.squareup.okio:okio:1.17.5'
|
||||||
implementation 'org.jmdns:jmdns:3.5.5'
|
implementation 'org.jmdns:jmdns:3.5.7'
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="app_label" translatable="false">Moonlight (Debug)</string>
|
|
||||||
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:dataExtractionRules="@xml/backup_rules_s"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:isGame="true"
|
android:isGame="true"
|
||||||
android:banner="@drawable/atv_banner"
|
android:banner="@drawable/atv_banner"
|
||||||
@@ -44,21 +45,28 @@
|
|||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:gwpAsanMode="always"
|
android:gwpAsanMode="always"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".PosterContentProvider"
|
android:name=".PosterContentProvider"
|
||||||
android:authorities="poster.${applicationId}"
|
android:authorities="poster.${applicationId}"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<!-- Samsung multi-window support -->
|
<!-- Samsung multi-window support -->
|
||||||
<uses-library
|
<uses-library
|
||||||
android:name="com.sec.android.app.multiwindow"
|
android:name="com.sec.android.app.multiwindow"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.sec.android.support.multiwindow"
|
android:name="com.sec.android.support.multiwindow"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
|
<!-- Disable Game Mode downscaling since it can break our UI dialogs and doesn't benefit
|
||||||
|
performance much for us since we don't use GL/Vulkan for rendering anyway -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.android.graphics.intervention.wm.allowDownscale"
|
||||||
|
android:value="false"/>
|
||||||
|
|
||||||
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
||||||
in each activity even though it is implied by targeting API 24+ -->
|
in each activity even though it is implied by targeting API 24+ -->
|
||||||
|
|
||||||
@@ -98,6 +106,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".preferences.StreamSettings"
|
android:name=".preferences.StreamSettings"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
|
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
android:label="Streaming Settings">
|
android:label="Streaming Settings">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
@@ -107,6 +116,7 @@
|
|||||||
android:name=".preferences.AddComputerManually"
|
android:name=".preferences.AddComputerManually"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:windowSoftInputMode="stateVisible"
|
android:windowSoftInputMode="stateVisible"
|
||||||
|
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
android:label="Add Computer Manually">
|
android:label="Add Computer Manually">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
|||||||
@@ -293,6 +293,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_app_view);
|
setContentView(R.layout.activity_app_view);
|
||||||
|
|
||||||
|
// Allow floating expanded PiP overlays while browsing apps
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
setShouldDockBigOverlays(false);
|
||||||
|
}
|
||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
|
|
||||||
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
@@ -88,7 +87,7 @@ import java.util.Locale;
|
|||||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||||
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
||||||
PerfOverlayListener
|
PerfOverlayListener, UsbDriverService.UsbDriverStateListener
|
||||||
{
|
{
|
||||||
private int lastButtonState = 0;
|
private int lastButtonState = 0;
|
||||||
|
|
||||||
@@ -108,6 +107,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||||
|
|
||||||
private ControllerHandler controllerHandler;
|
private ControllerHandler controllerHandler;
|
||||||
|
private KeyboardTranslator keyboardTranslator;
|
||||||
private VirtualController virtualController;
|
private VirtualController virtualController;
|
||||||
|
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
@@ -118,8 +118,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private boolean displayedFailureDialog = false;
|
private boolean displayedFailureDialog = false;
|
||||||
private boolean connecting = false;
|
private boolean connecting = false;
|
||||||
private boolean connected = false;
|
private boolean connected = false;
|
||||||
|
private boolean autoEnterPip = false;
|
||||||
private boolean surfaceCreated = false;
|
private boolean surfaceCreated = false;
|
||||||
private boolean attemptedConnection = false;
|
private boolean attemptedConnection = false;
|
||||||
|
private int suppressPipRefCount = 0;
|
||||||
|
private String pcName;
|
||||||
|
private String appName;
|
||||||
|
|
||||||
private InputCaptureProvider inputCaptureProvider;
|
private InputCaptureProvider inputCaptureProvider;
|
||||||
private int modifierFlags = 0;
|
private int modifierFlags = 0;
|
||||||
@@ -150,6 +154,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
||||||
binder.setListener(controllerHandler);
|
binder.setListener(controllerHandler);
|
||||||
|
binder.setStateListener(Game.this);
|
||||||
|
binder.start();
|
||||||
connectedToUsbDriverService = true;
|
connectedToUsbDriverService = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,9 +220,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||||
|
|
||||||
if (prefConfig.stretchVideo) {
|
if (prefConfig.stretchVideo || shouldIgnoreInsetsForResolution(prefConfig.width, prefConfig.height)) {
|
||||||
// Allow the activity to layout under notches if the fill-screen option
|
// Allow the activity to layout under notches if the fill-screen option
|
||||||
// was turned on by the user
|
// was turned on by the user or it's a full-screen native resolution
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||||
@@ -269,13 +275,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
lowLatencyWifiLock.acquire();
|
lowLatencyWifiLock.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||||
|
pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||||
|
|
||||||
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
||||||
String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
|
||||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
||||||
boolean willStreamHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
|
||||||
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
||||||
|
|
||||||
X509Certificate serverCert = null;
|
X509Certificate serverCert = null;
|
||||||
@@ -301,7 +308,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
shortcutHelper.reportComputerShortcutUsed(computer);
|
shortcutHelper.reportComputerShortcutUsed(computer);
|
||||||
if (appName != null) {
|
if (appName != null) {
|
||||||
// This may be null if launched from the "Resume Session" PC context menu item
|
// This may be null if launched from the "Resume Session" PC context menu item
|
||||||
shortcutHelper.reportGameLaunched(computer, new NvApp(appName, appId, willStreamHdr));
|
shortcutHelper.reportGameLaunched(computer, new NvApp(appName, appId, appSupportsHdr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the MediaCodec helper before creating the decoder
|
// Initialize the MediaCodec helper before creating the decoder
|
||||||
@@ -309,42 +316,33 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
MediaCodecHelper.initialize(this, glPrefs.glRenderer);
|
MediaCodecHelper.initialize(this, glPrefs.glRenderer);
|
||||||
|
|
||||||
// Check if the user has enabled HDR
|
// Check if the user has enabled HDR
|
||||||
|
boolean willStreamHdr = false;
|
||||||
if (prefConfig.enableHdr) {
|
if (prefConfig.enableHdr) {
|
||||||
// Check if the app supports it
|
// Start our HDR checklist
|
||||||
if (!willStreamHdr) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
Toast.makeText(this, "This game does not support HDR10", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
// It does, so start our HDR checklist
|
|
||||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
// We already know the app supports HDR if willStreamHdr is set.
|
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
Display.HdrCapabilities hdrCaps = display.getHdrCapabilities();
|
Display.HdrCapabilities hdrCaps = display.getHdrCapabilities();
|
||||||
|
|
||||||
// We must now ensure our display is compatible with HDR10
|
// We must now ensure our display is compatible with HDR10
|
||||||
boolean foundHdr10 = false;
|
|
||||||
if (hdrCaps != null) {
|
if (hdrCaps != null) {
|
||||||
// getHdrCapabilities() returns null on Lenovo Lenovo Mirage Solo (vega), Android 8.0
|
// getHdrCapabilities() returns null on Lenovo Lenovo Mirage Solo (vega), Android 8.0
|
||||||
for (int hdrType : hdrCaps.getSupportedHdrTypes()) {
|
for (int hdrType : hdrCaps.getSupportedHdrTypes()) {
|
||||||
if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) {
|
if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) {
|
||||||
foundHdr10 = true;
|
willStreamHdr = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundHdr10) {
|
if (!willStreamHdr) {
|
||||||
// Nope, no HDR for us :(
|
// Nope, no HDR for us :(
|
||||||
willStreamHdr = false;
|
|
||||||
Toast.makeText(this, "Display does not support HDR10", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Display does not support HDR10", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Toast.makeText(this, "HDR requires Android 7.0 or later", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "HDR requires Android 7.0 or later", Toast.LENGTH_LONG).show();
|
||||||
willStreamHdr = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
willStreamHdr = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user has enabled performance stats overlay
|
// Check if the user has enabled performance stats overlay
|
||||||
if (prefConfig.enablePerfOverlay) {
|
if (prefConfig.enablePerfOverlay) {
|
||||||
@@ -377,9 +375,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
Toast.makeText(this, "Decoder does not support HEVC Main10HDR10", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Decoder does not support HEVC Main10HDR10", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
// Display a message to the user if HEVC was forced on but we still didn't find a decoder
|
||||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||||
Toast.makeText(this, "No H.265 decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "No HEVC decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
|
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
|
||||||
@@ -398,52 +396,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
float displayRefreshRate = prepareDisplayForRendering();
|
float displayRefreshRate = prepareDisplayForRendering();
|
||||||
LimeLog.info("Display refresh rate: "+displayRefreshRate);
|
LimeLog.info("Display refresh rate: "+displayRefreshRate);
|
||||||
|
|
||||||
// HACK: Despite many efforts to ensure low latency consistent frame
|
// If the user requested frame pacing using a capped FPS, we will need to change our
|
||||||
// delivery, the best non-lossy mechanism is to buffer 1 extra frame
|
// desired FPS setting here in accordance with the active display refresh rate.
|
||||||
// in the output pipeline. Android does some buffering on its end
|
|
||||||
// in SurfaceFlinger and it's difficult (impossible?) to inspect
|
|
||||||
// the precise state of the buffer queue to the screen after we
|
|
||||||
// release a frame for rendering.
|
|
||||||
//
|
|
||||||
// Since buffering a frame adds latency and we are primarily a
|
|
||||||
// latency-optimized client, rather than one designed for picture-perfect
|
|
||||||
// accuracy, we will synthetically induce a negative pressure on the display
|
|
||||||
// output pipeline by driving the decoder input pipeline under the speed
|
|
||||||
// that the display can refresh. This ensures a constant negative pressure
|
|
||||||
// to keep latency down but does induce a periodic frame loss. However, this
|
|
||||||
// periodic frame loss is *way* less than what we'd already get in Marshmallow's
|
|
||||||
// display pipeline where frames are dropped outside of our control if they land
|
|
||||||
// on the same V-sync.
|
|
||||||
//
|
|
||||||
// Hopefully, we can get rid of this once someone comes up with a better way
|
|
||||||
// to track the state of the pipeline and time frames.
|
|
||||||
int roundedRefreshRate = Math.round(displayRefreshRate);
|
int roundedRefreshRate = Math.round(displayRefreshRate);
|
||||||
int chosenFrameRate = prefConfig.fps;
|
int chosenFrameRate = prefConfig.fps;
|
||||||
if (!prefConfig.disableFrameDrop || prefConfig.unlockFps) {
|
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
|
||||||
if (Build.DEVICE.equals("coral") || Build.DEVICE.equals("flame")) {
|
if (prefConfig.fps >= roundedRefreshRate) {
|
||||||
// HACK: Pixel 4 (XL) ignores the preferred display mode and lowers refresh rate,
|
if (prefConfig.fps > roundedRefreshRate + 3) {
|
||||||
// causing frame pacing issues. See https://issuetracker.google.com/issues/143401475
|
|
||||||
// To work around this, use frame drop mode if we want to stream at >= 60 FPS.
|
|
||||||
if (prefConfig.fps >= 60) {
|
|
||||||
LimeLog.info("Using Pixel 4 rendering hack");
|
|
||||||
decoderRenderer.enableLegacyFrameDropRendering();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (prefConfig.fps >= roundedRefreshRate) {
|
|
||||||
if (prefConfig.unlockFps) {
|
|
||||||
// Use frame drops when rendering above the screen frame rate
|
// Use frame drops when rendering above the screen frame rate
|
||||||
decoderRenderer.enableLegacyFrameDropRendering();
|
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
|
||||||
LimeLog.info("Using drop mode for FPS > Hz");
|
LimeLog.info("Using drop mode for FPS > Hz");
|
||||||
} else if (roundedRefreshRate <= 49) {
|
} else if (roundedRefreshRate <= 49) {
|
||||||
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
|
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
|
||||||
decoderRenderer.enableLegacyFrameDropRendering();
|
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
|
||||||
LimeLog.info("Bogus refresh rate: " + roundedRefreshRate);
|
LimeLog.info("Bogus refresh rate: " + roundedRefreshRate);
|
||||||
}
|
}
|
||||||
// HACK: Avoid crashing on some MTK devices
|
else {
|
||||||
else if (decoderRenderer.isBlacklistedForFrameRate(roundedRefreshRate - 1)) {
|
|
||||||
// Use the old rendering strategy on these broken devices
|
|
||||||
decoderRenderer.enableLegacyFrameDropRendering();
|
|
||||||
} else {
|
|
||||||
chosenFrameRate = roundedRefreshRate - 1;
|
chosenFrameRate = roundedRefreshRate - 1;
|
||||||
LimeLog.info("Adjusting FPS target for screen to " + chosenFrameRate);
|
LimeLog.info("Adjusting FPS target for screen to " + chosenFrameRate);
|
||||||
}
|
}
|
||||||
@@ -459,7 +427,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
.setResolution(prefConfig.width, prefConfig.height)
|
.setResolution(prefConfig.width, prefConfig.height)
|
||||||
.setLaunchRefreshRate(prefConfig.fps)
|
.setLaunchRefreshRate(prefConfig.fps)
|
||||||
.setRefreshRate(chosenFrameRate)
|
.setRefreshRate(chosenFrameRate)
|
||||||
.setApp(new NvApp(appName != null ? appName : "app", appId, willStreamHdr))
|
.setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr))
|
||||||
.setBitrate(prefConfig.bitrate)
|
.setBitrate(prefConfig.bitrate)
|
||||||
.setEnableSops(prefConfig.enableSops)
|
.setEnableSops(prefConfig.enableSops)
|
||||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||||
@@ -473,14 +441,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
.setAttachedGamepadMask(gamepadMask)
|
.setAttachedGamepadMask(gamepadMask)
|
||||||
.setClientRefreshRateX100((int)(displayRefreshRate * 100))
|
.setClientRefreshRateX100((int)(displayRefreshRate * 100))
|
||||||
.setAudioConfiguration(prefConfig.audioConfiguration)
|
.setAudioConfiguration(prefConfig.audioConfiguration)
|
||||||
|
.setAudioEncryption(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Initialize the connection
|
// Initialize the connection
|
||||||
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
||||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||||
|
keyboardTranslator = new KeyboardTranslator();
|
||||||
|
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||||
|
inputManager.registerInputDeviceListener(keyboardTranslator, null);
|
||||||
|
|
||||||
// Initialize touch contexts
|
// Initialize touch contexts
|
||||||
for (int i = 0; i < touchContextMap.length; i++) {
|
for (int i = 0; i < touchContextMap.length; i++) {
|
||||||
@@ -490,7 +461,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
else {
|
else {
|
||||||
touchContextMap[i] = new RelativeTouchContext(conn, i,
|
touchContextMap[i] = new RelativeTouchContext(conn, i,
|
||||||
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
||||||
streamView);
|
streamView, prefConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,6 +522,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
performanceOverlayView.setVisibility(View.GONE);
|
performanceOverlayView.setVisibility(View.GONE);
|
||||||
notificationOverlayView.setVisibility(View.GONE);
|
notificationOverlayView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're in PiP (still gaming, but interruptible)
|
||||||
|
UiHelper.notifyStreamEnteringPiP(this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
isHidingOverlays = false;
|
isHidingOverlays = false;
|
||||||
@@ -566,27 +540,70 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
|
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're out of PiP (gaming, non-interruptible)
|
||||||
|
UiHelper.notifyStreamExitingPiP(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
private PictureInPictureParams getPictureInPictureParams(boolean autoEnter) {
|
||||||
|
PictureInPictureParams.Builder builder =
|
||||||
|
new PictureInPictureParams.Builder()
|
||||||
|
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
||||||
|
.setSourceRectHint(new Rect(
|
||||||
|
streamView.getLeft(), streamView.getTop(),
|
||||||
|
streamView.getRight(), streamView.getBottom()));
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
builder.setAutoEnterEnabled(autoEnter);
|
||||||
|
builder.setSeamlessResizeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (appName != null) {
|
||||||
|
builder.setTitle(appName);
|
||||||
|
if (pcName != null) {
|
||||||
|
builder.setSubtitle(pcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pcName != null) {
|
||||||
|
builder.setTitle(pcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePipAutoEnter() {
|
||||||
|
if (!prefConfig.enablePip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean autoEnter = connected && suppressPipRefCount == 0;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
autoEnterPip = autoEnter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserLeaveHint() {
|
public void onUserLeaveHint() {
|
||||||
super.onUserLeaveHint();
|
super.onUserLeaveHint();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// PiP is only supported on Oreo and later, and we don't need to manually enter PiP on
|
||||||
if (prefConfig.enablePip && connected) {
|
// Android S and later.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
if (autoEnterPip) {
|
||||||
try {
|
try {
|
||||||
// This has thrown all sorts of weird exceptions on Samsung devices
|
// This has thrown all sorts of weird exceptions on Samsung devices
|
||||||
// running Oreo. Just eat them and close gracefully on leave, rather
|
// running Oreo. Just eat them and close gracefully on leave, rather
|
||||||
// than crashing.
|
// than crashing.
|
||||||
enterPictureInPictureMode(
|
enterPictureInPictureMode(getPictureInPictureParams(false));
|
||||||
new PictureInPictureParams.Builder()
|
|
||||||
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
|
||||||
.setSourceRectHint(new Rect(
|
|
||||||
streamView.getLeft(), streamView.getTop(),
|
|
||||||
streamView.getRight(), streamView.getBottom()))
|
|
||||||
.build());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -602,23 +619,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// lifted while focus was not on us. Clear the modifier state.
|
// lifted while focus was not on us. Clear the modifier state.
|
||||||
this.modifierFlags = 0;
|
this.modifierFlags = 0;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// With Android native pointer capture, capture is lost when focus is lost,
|
||||||
// Capture is lost when focus is lost, so it must be requested again
|
// so it must be requested again when focus is regained.
|
||||||
// when focus is regained.
|
inputCaptureProvider.onWindowFocusChanged(hasFocus);
|
||||||
if (inputCaptureProvider.isCapturingEnabled() && hasFocus) {
|
}
|
||||||
// Recapture the pointer if focus was regained. On Android Q,
|
|
||||||
// we have to delay a bit before requesting capture because otherwise
|
private boolean isRefreshRateEqualMatch(float refreshRate) {
|
||||||
// we'll hit the "requestPointerCapture called for a window that has no focus"
|
return refreshRate >= prefConfig.fps &&
|
||||||
// error and it will not actually capture the cursor.
|
refreshRate <= prefConfig.fps + 3;
|
||||||
Handler h = new Handler();
|
|
||||||
h.postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
streamView.requestPointerCapture();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRefreshRateGoodMatch(float refreshRate) {
|
private boolean isRefreshRateGoodMatch(float refreshRate) {
|
||||||
@@ -626,6 +634,26 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
Math.round(refreshRate) % prefConfig.fps <= 3;
|
Math.round(refreshRate) % prefConfig.fps <= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldIgnoreInsetsForResolution(int width, int height) {
|
||||||
|
// Never ignore insets for non-native resolutions
|
||||||
|
if (!PreferenceConfiguration.isNativeResolution(width, height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
|
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||||
|
// Ignore insets if this is an exact match for the display resolution
|
||||||
|
if ((width == candidate.getPhysicalWidth() && height == candidate.getPhysicalHeight()) ||
|
||||||
|
(height == candidate.getPhysicalWidth() && width == candidate.getPhysicalHeight())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private float prepareDisplayForRendering() {
|
private float prepareDisplayForRendering() {
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||||
@@ -636,6 +664,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
Display.Mode bestMode = display.getMode();
|
Display.Mode bestMode = display.getMode();
|
||||||
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
|
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
|
||||||
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
|
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
|
||||||
|
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
|
||||||
for (Display.Mode candidate : display.getSupportedModes()) {
|
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||||
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
|
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
|
||||||
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
|
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
|
||||||
@@ -667,12 +696,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshRateIsGood) {
|
if (prefConfig.framePacing != PreferenceConfiguration.FRAME_PACING_MIN_LATENCY &&
|
||||||
// We have a good matching refresh rate, so we're looking for equal or greater
|
refreshRateIsEqual && !isRefreshRateEqualMatch(candidate.getRefreshRate())) {
|
||||||
// that is also a good matching refresh rate for our stream frame rate.
|
// If we had an equal refresh rate and this one is not, skip it. In min latency
|
||||||
if (refreshRateReduced || !isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
// mode, we want to always prefer the highest frame rate even though it may cause
|
||||||
|
// microstuttering.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (refreshRateIsGood) {
|
||||||
|
// We've already got a good match, so if this one isn't also good, it's not
|
||||||
|
// worth considering at all.
|
||||||
|
if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't want ever reduce our refresh rate unless we found an exact
|
||||||
|
// match and we're not in min latency mode.
|
||||||
|
if (refreshRateReduced) {
|
||||||
|
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MIN_LATENCY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!isRefreshRateEqualMatch(candidate.getRefreshRate())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
||||||
// We didn't have a good match and this match isn't good either, so just don't
|
// We didn't have a good match and this match isn't good either, so just don't
|
||||||
@@ -689,6 +736,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
bestMode = candidate;
|
bestMode = candidate;
|
||||||
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
|
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
|
||||||
|
refreshRateIsEqual = isRefreshRateEqualMatch(candidate.getRefreshRate());
|
||||||
}
|
}
|
||||||
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||||
@@ -769,9 +817,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return displayRefreshRate;
|
return displayRefreshRate;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Use the actual refresh rate of the display, since the preferred refresh rate or mode
|
// Use the lower of the current refresh rate and the selected refresh rate.
|
||||||
// may not actually be applied (ex: Pixel 4 with Smooth Display disabled).
|
// The preferred refresh rate may not actually be applied (ex: Battery Saver mode).
|
||||||
return getWindowManager().getDefaultDisplay().getRefreshRate();
|
return Math.min(getWindowManager().getDefaultDisplay().getRefreshRate(), displayRefreshRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,6 +827,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private final Runnable hideSystemUi = new Runnable() {
|
private final Runnable hideSystemUi = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// TODO: Do we want to use WindowInsetsController here on R+ instead of
|
||||||
|
// SYSTEM_UI_FLAG_IMMERSIVE_STICKY? They seem to do the same thing as of S...
|
||||||
|
|
||||||
// In multi-window mode on N+, we need to drop our layout flags or we'll
|
// In multi-window mode on N+, we need to drop our layout flags or we'll
|
||||||
// be drawing underneath the system UI.
|
// be drawing underneath the system UI.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode()) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode()) {
|
||||||
@@ -843,10 +894,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
if (controllerHandler != null) {
|
if (controllerHandler != null) {
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
|
||||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||||
}
|
}
|
||||||
|
if (keyboardTranslator != null) {
|
||||||
|
inputManager.unregisterInputDeviceListener(keyboardTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
if (lowLatencyWifiLock != null) {
|
if (lowLatencyWifiLock != null) {
|
||||||
lowLatencyWifiLock.release();
|
lowLatencyWifiLock.release();
|
||||||
@@ -898,10 +952,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Add the video codec to the post-stream toast
|
// Add the video codec to the post-stream toast
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||||
message += " [H.265 HDR]";
|
message += " [HEVC HDR]";
|
||||||
}
|
}
|
||||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||||
message += " [H.265]";
|
message += " [HEVC]";
|
||||||
}
|
}
|
||||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||||
message += " [H.264]";
|
message += " [H.264]";
|
||||||
@@ -1000,6 +1054,26 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We cannot simply use modifierFlags for all key event processing, because
|
||||||
|
// some IMEs will not generate real key events for pressing Shift. Instead
|
||||||
|
// they will simply send key events with isShiftPressed() returning true,
|
||||||
|
// and we will need to send the modifier flag ourselves.
|
||||||
|
private byte getModifierState(KeyEvent event) {
|
||||||
|
// Start with the global modifier state to ensure we cover the case
|
||||||
|
// detailed in https://github.com/moonlight-stream/moonlight-android/issues/840
|
||||||
|
byte modifier = getModifierState();
|
||||||
|
if (event.isShiftPressed()) {
|
||||||
|
modifier |= KeyboardPacket.MODIFIER_SHIFT;
|
||||||
|
}
|
||||||
|
if (event.isCtrlPressed()) {
|
||||||
|
modifier |= KeyboardPacket.MODIFIER_CTRL;
|
||||||
|
}
|
||||||
|
if (event.isAltPressed()) {
|
||||||
|
modifier |= KeyboardPacket.MODIFIER_ALT;
|
||||||
|
}
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
|
||||||
private byte getModifierState() {
|
private byte getModifierState() {
|
||||||
return (byte) modifierFlags;
|
return (byte) modifierFlags;
|
||||||
}
|
}
|
||||||
@@ -1019,8 +1093,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Handle a synthetic back button event that some Android OS versions
|
// Handle a synthetic back button event that some Android OS versions
|
||||||
// create as a result of a right-click. This event WILL repeat if
|
// create as a result of a right-click. This event WILL repeat if
|
||||||
// the right mouse button is held down, so we ignore those.
|
// the right mouse button is held down, so we ignore those.
|
||||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
int eventSource = event.getSource();
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
if ((eventSource == InputDevice.SOURCE_MOUSE ||
|
||||||
|
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||||
|
|
||||||
// Send the right mouse button event if mouse back and forward
|
// Send the right mouse button event if mouse back and forward
|
||||||
@@ -1045,7 +1120,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1065,10 +1140,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte modifiers = getModifierState();
|
byte modifiers = getModifierState(event);
|
||||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_DOWN, modifiers);
|
|
||||||
}
|
}
|
||||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
|
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
|
||||||
}
|
}
|
||||||
@@ -1090,8 +1164,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Handle a synthetic back button event that some Android OS versions
|
// Handle a synthetic back button event that some Android OS versions
|
||||||
// create as a result of a right-click.
|
// create as a result of a right-click.
|
||||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
int eventSource = event.getSource();
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
if ((eventSource == InputDevice.SOURCE_MOUSE ||
|
||||||
|
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||||
|
|
||||||
// Send the right mouse button event if mouse back and forward
|
// Send the right mouse button event if mouse back and forward
|
||||||
@@ -1115,7 +1190,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1129,14 +1204,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte modifiers = getModifierState();
|
byte modifiers = getModifierState(event);
|
||||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||||
}
|
}
|
||||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
|
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
|
||||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
|
||||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_UP, getModifierState());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1167,18 +1239,20 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
int eventSource = event.getSource();
|
||||||
|
if ((eventSource & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
if (controllerHandler.handleMotionEvent(event)) {
|
if (controllerHandler.handleMotionEvent(event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
else if ((eventSource & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE)
|
(eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 ||
|
||||||
|
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE)
|
||||||
{
|
{
|
||||||
// This case is for mice and non-finger touch devices
|
// This case is for mice and non-finger touch devices
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if (eventSource == InputDevice.SOURCE_MOUSE ||
|
||||||
event.getSource() == InputDevice.SOURCE_TOUCHPAD ||
|
(eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 || // SOURCE_TOUCHPAD
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||||
(event.getPointerCount() >= 1 &&
|
(event.getPointerCount() >= 1 &&
|
||||||
(event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
|
(event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
|
||||||
event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS ||
|
event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS ||
|
||||||
@@ -1202,18 +1276,48 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
|
short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
|
||||||
|
|
||||||
if (deltaX != 0 || deltaY != 0) {
|
if (deltaX != 0 || deltaY != 0) {
|
||||||
conn.sendMouseMove(deltaX, deltaY);
|
if (prefConfig.absoluteMouseMode) {
|
||||||
|
conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)view.getWidth(), (short)view.getHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseMove(deltaX, deltaY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
|
||||||
|
// If this input device is not associated with the view itself (like a trackpad),
|
||||||
|
// we'll convert the device-specific coordinates to use to send the cursor position.
|
||||||
|
// This really isn't ideal but it's probably better than nothing.
|
||||||
|
//
|
||||||
|
// Trackpad on newer versions of Android (Oreo and later) should be caught by the
|
||||||
|
// relative axes case above. If we get here, we're on an older version that doesn't
|
||||||
|
// support pointer capture.
|
||||||
|
InputDevice device = event.getDevice();
|
||||||
|
if (device != null) {
|
||||||
|
InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X, eventSource);
|
||||||
|
InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y, eventSource);
|
||||||
|
|
||||||
|
// All touchpads coordinate planes should start at (0, 0)
|
||||||
|
if (xRange != null && yRange != null && xRange.getMin() == 0 && yRange.getMin() == 0) {
|
||||||
|
int xMax = (int)xRange.getMax();
|
||||||
|
int yMax = (int)yRange.getMax();
|
||||||
|
|
||||||
|
// Touchpads must be smaller than (65535, 65535)
|
||||||
|
if (xMax <= Short.MAX_VALUE && yMax <= Short.MAX_VALUE) {
|
||||||
|
conn.sendMousePosition((short)event.getX(), (short)event.getY(),
|
||||||
|
(short)xMax, (short)yMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (view != null) {
|
else if (view != null) {
|
||||||
// Otherwise send absolute position
|
// Otherwise send absolute position based on the view for SOURCE_CLASS_POINTER
|
||||||
updateMousePosition(view, event);
|
updateMousePosition(view, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||||
// Send the vertical scroll packet
|
// Send the vertical scroll packet
|
||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120));
|
||||||
conn.sendMouseScroll(vScrollClicks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||||
@@ -1356,7 +1460,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
if (event.getPointerCount() == 1) {
|
if (event.getPointerCount() == 1 &&
|
||||||
|
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || (event.getFlags() & MotionEvent.FLAG_CANCELED) == 0)) {
|
||||||
// All fingers up
|
// All fingers up
|
||||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||||
// This is a 3 finger tap to bring up the keyboard
|
// This is a 3 finger tap to bring up the keyboard
|
||||||
@@ -1364,7 +1469,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.touchUpEvent(eventX, eventY);
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && (event.getFlags() & MotionEvent.FLAG_CANCELED) != 0) {
|
||||||
|
context.cancelTouch();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.touchUpEvent(eventX, eventY);
|
||||||
|
}
|
||||||
|
|
||||||
for (TouchContext touchContext : touchContextMap) {
|
for (TouchContext touchContext : touchContextMap) {
|
||||||
touchContext.setPointerCount(event.getPointerCount() - 1);
|
touchContext.setPointerCount(event.getPointerCount() - 1);
|
||||||
}
|
}
|
||||||
@@ -1502,9 +1614,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private void stopConnection() {
|
private void stopConnection() {
|
||||||
if (connecting || connected) {
|
if (connecting || connected) {
|
||||||
connecting = connected = false;
|
connecting = connected = false;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
|
||||||
controllerHandler.stop();
|
controllerHandler.stop();
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're no longer in game
|
||||||
|
UiHelper.notifyStreamEnded(this);
|
||||||
|
|
||||||
// Stop may take a few hundred ms to do some network I/O to tell
|
// Stop may take a few hundred ms to do some network I/O to tell
|
||||||
// the server we're going away and clean up. Let it run in a separate
|
// the server we're going away and clean up. Let it run in a separate
|
||||||
// thread to keep things smooth for the UI. Inside moonlight-common,
|
// thread to keep things smooth for the UI. Inside moonlight-common,
|
||||||
@@ -1599,6 +1715,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MoonBridge.ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
case MoonBridge.ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
||||||
|
case MoonBridge.ML_ERROR_PROTECTED_CONTENT:
|
||||||
message = getResources().getString(R.string.early_termination_error);
|
message = getResources().getString(R.string.early_termination_error);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1666,6 +1783,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
connected = true;
|
connected = true;
|
||||||
connecting = false;
|
connecting = false;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
|
||||||
// Hide the mouse cursor now after a short delay.
|
// Hide the mouse cursor now after a short delay.
|
||||||
// Doing it before dismissing the spinner seems to be undone
|
// Doing it before dismissing the spinner seems to be undone
|
||||||
@@ -1683,6 +1801,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Keep the display on
|
// Keep the display on
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're in game
|
||||||
|
UiHelper.notifyStreamConnected(Game.this);
|
||||||
|
|
||||||
hideSystemUi(1000);
|
hideSystemUi(1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1717,6 +1838,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
controllerHandler.handleRumble(controllerNumber, lowFreqMotor, highFreqMotor);
|
controllerHandler.handleRumble(controllerNumber, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHdrMode(boolean enabled) {
|
||||||
|
LimeLog.info("Display HDR mode: " + (enabled ? "enabled" : "disabled"));
|
||||||
|
decoderRenderer.setHdrMode(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
if (!surfaceCreated) {
|
if (!surfaceCreated) {
|
||||||
@@ -1726,6 +1853,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (!attemptedConnection) {
|
if (!attemptedConnection) {
|
||||||
attemptedConnection = true;
|
attemptedConnection = true;
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're "loading" while connecting
|
||||||
|
UiHelper.notifyStreamConnecting(Game.this);
|
||||||
|
|
||||||
decoderRenderer.setRenderTarget(holder);
|
decoderRenderer.setRenderTarget(holder);
|
||||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||||
}
|
}
|
||||||
@@ -1803,7 +1933,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||||
short keyMap = KeyboardTranslator.translate(keyCode);
|
short keyMap = keyboardTranslator.translate(keyCode, -1);
|
||||||
if (keyMap != 0) {
|
if (keyMap != 0) {
|
||||||
// handleSpecialKeys() takes the Android keycode
|
// handleSpecialKeys() takes the Android keycode
|
||||||
if (handleSpecialKeys(keyCode, buttonDown)) {
|
if (handleSpecialKeys(keyCode, buttonDown)) {
|
||||||
@@ -1851,4 +1981,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUsbPermissionPromptStarting() {
|
||||||
|
// Disable PiP auto-enter while the USB permission prompt is on-screen. This prevents
|
||||||
|
// us from entering PiP while the user is interacting with the OS permission dialog.
|
||||||
|
suppressPipRefCount++;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUsbPermissionPromptCompleted() {
|
||||||
|
suppressPipRefCount--;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,12 @@ package com.limelight;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
import android.window.OnBackInvokedCallback;
|
||||||
|
import android.window.OnBackInvokedDispatcher;
|
||||||
|
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
|
|
||||||
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
|
|||||||
private SpinnerDialog loadingDialog;
|
private SpinnerDialog loadingDialog;
|
||||||
private WebView webView;
|
private WebView webView;
|
||||||
|
|
||||||
|
private boolean backCallbackRegistered;
|
||||||
|
private OnBackInvokedCallback onBackInvokedCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
onBackInvokedCallback = new OnBackInvokedCallback() {
|
||||||
|
@Override
|
||||||
|
public void onBackInvoked() {
|
||||||
|
// We should always be able to go back because we unregister our callback
|
||||||
|
// when we can't go back. Nonetheless, we will still check anyway.
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
webView = new WebView(this);
|
webView = new WebView(this);
|
||||||
setContentView(webView);
|
setContentView(webView);
|
||||||
|
|
||||||
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
|
|||||||
getResources().getString(R.string.help_loading_title),
|
getResources().getString(R.string.help_loading_title),
|
||||||
getResources().getString(R.string.help_loading_msg), false);
|
getResources().getString(R.string.help_loading_msg), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshBackDispatchState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
|
|||||||
loadingDialog.dismiss();
|
loadingDialog.dismiss();
|
||||||
loadingDialog = null;
|
loadingDialog = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshBackDispatchState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,6 +82,31 @@ public class HelpActivity extends Activity {
|
|||||||
webView.loadUrl(getIntent().getData().toString());
|
webView.loadUrl(getIntent().getData().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshBackDispatchState() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (webView.canGoBack() && !backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||||
|
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
|
||||||
|
backCallbackRegistered = true;
|
||||||
|
}
|
||||||
|
else if (!webView.canGoBack() && backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||||
|
backCallbackRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
// Back goes back through the WebView history
|
// Back goes back through the WebView history
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
|
|
||||||
|
// Allow floating expanded PiP overlays while browsing PCs
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
setShouldDockBigOverlays(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Set default preferences if we've never been run
|
// Set default preferences if we've never been run
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||||
|
|
||||||
@@ -155,6 +160,13 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Amazon review didn't like the help button because the wiki was not entirely
|
||||||
|
// navigable via the Fire TV remote (though the relevant parts were). Let's hide
|
||||||
|
// it on Fire TV.
|
||||||
|
if (getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||||
|
helpButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
@@ -367,8 +379,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doPair(final ComputerDetails computer) {
|
private void doPair(final ComputerDetails computer) {
|
||||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -505,8 +516,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doUnpair(final ComputerDetails computer) {
|
private void doUnpair(final ComputerDetails computer) {
|
||||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ import com.limelight.computers.ComputerManagerService;
|
|||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
import com.limelight.nvstream.http.PairingManager;
|
import com.limelight.nvstream.http.PairingManager;
|
||||||
|
import com.limelight.nvstream.wol.WakeOnLanSender;
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
import com.limelight.utils.ServerHelper;
|
import com.limelight.utils.ServerHelper;
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ public class ShortcutTrampoline extends Activity {
|
|||||||
private NvApp app;
|
private NvApp app;
|
||||||
private ArrayList<Intent> intentStack = new ArrayList<>();
|
private ArrayList<Intent> intentStack = new ArrayList<>();
|
||||||
|
|
||||||
|
private int wakeHostTries = 10;
|
||||||
private ComputerDetails computer;
|
private ComputerDetails computer;
|
||||||
private SpinnerDialog blockingLoadSpinner;
|
private SpinnerDialog blockingLoadSpinner;
|
||||||
|
|
||||||
@@ -79,6 +82,23 @@ public class ShortcutTrampoline extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to wake the target PC if it's offline (up to some retry limit)
|
||||||
|
if (details.state == ComputerDetails.State.OFFLINE && --wakeHostTries >= 0) {
|
||||||
|
try {
|
||||||
|
// Make a best effort attempt to wake the target PC
|
||||||
|
WakeOnLanSender.sendWolPacket(computer);
|
||||||
|
|
||||||
|
// If we sent at least one WoL packet, reset the computer state
|
||||||
|
// to force ComputerManager to poll it again.
|
||||||
|
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// If we got an exception, we couldn't send a single WoL packet,
|
||||||
|
// so fallthrough into the offline error path.
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (details.state != ComputerDetails.State.UNKNOWN) {
|
if (details.state != ComputerDetails.State.UNKNOWN) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.limelight.binding.input;
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
@@ -7,9 +8,12 @@ import android.hardware.usb.UsbDevice;
|
|||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.CombinedVibration;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.os.VibrationAttributes;
|
||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.os.VibratorManager;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
@@ -21,6 +25,7 @@ import com.limelight.LimeLog;
|
|||||||
import com.limelight.binding.input.driver.AbstractController;
|
import com.limelight.binding.input.driver.AbstractController;
|
||||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
|
import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.ControllerPacket;
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
@@ -57,6 +62,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||||
private final GameGestures gestures;
|
private final GameGestures gestures;
|
||||||
private final Vibrator deviceVibrator;
|
private final Vibrator deviceVibrator;
|
||||||
|
private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler;
|
||||||
private boolean hasGameController;
|
private boolean hasGameController;
|
||||||
|
|
||||||
private final PreferenceConfiguration prefConfig;
|
private final PreferenceConfiguration prefConfig;
|
||||||
@@ -68,10 +74,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
this.gestures = gestures;
|
this.gestures = gestures;
|
||||||
this.prefConfig = prefConfig;
|
this.prefConfig = prefConfig;
|
||||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext);
|
||||||
|
|
||||||
// HACK: For now we're hardcoding a 7% deadzone. Some deadzone
|
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||||
// is required for controller batching support to work.
|
|
||||||
int deadzonePercentage = 7;
|
|
||||||
|
|
||||||
int[] ids = InputDevice.getDeviceIds();
|
int[] ids = InputDevice.getDeviceIds();
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
@@ -155,11 +160,36 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This can happen when gaining/losing input focus with some devices.
|
||||||
|
// Input devices that have a trackpad may gain/lose AXIS_RELATIVE_X/Y.
|
||||||
@Override
|
@Override
|
||||||
public void onInputDeviceChanged(int deviceId) {
|
public void onInputDeviceChanged(int deviceId) {
|
||||||
// Remove and re-add
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
onInputDeviceRemoved(deviceId);
|
if (device == null) {
|
||||||
onInputDeviceAdded(deviceId);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have a context for this device, we don't need to update anything
|
||||||
|
InputDeviceContext existingContext = inputDeviceContexts.get(deviceId);
|
||||||
|
if (existingContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Device changed: "+existingContext.name+" ("+deviceId+")");
|
||||||
|
|
||||||
|
// Don't release the controller number, because we will carry it over if it is present.
|
||||||
|
// We also want to make sure the change is invisible to the host PC to avoid an add/remove
|
||||||
|
// cycle for the gamepad which may break some games.
|
||||||
|
existingContext.destroy();
|
||||||
|
|
||||||
|
InputDeviceContext newContext = createInputDeviceContextForDevice(device);
|
||||||
|
|
||||||
|
// Copy over existing controller number state
|
||||||
|
newContext.assignedControllerNumber = existingContext.assignedControllerNumber;
|
||||||
|
newContext.reservedControllerNumber = existingContext.reservedControllerNumber;
|
||||||
|
newContext.controllerNumber = existingContext.controllerNumber;
|
||||||
|
|
||||||
|
inputDeviceContexts.put(deviceId, newContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
@@ -173,6 +203,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
deviceContext.destroy();
|
deviceContext.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shieldControllerExtensionsHandler.destroy();
|
||||||
deviceVibrator.cancel();
|
deviceVibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,6 +509,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
LimeLog.info(dev.toString());
|
LimeLog.info(dev.toString());
|
||||||
|
|
||||||
|
context.inputDevice = dev;
|
||||||
context.name = devName;
|
context.name = devName;
|
||||||
context.id = dev.getId();
|
context.id = dev.getId();
|
||||||
context.external = isExternal(dev);
|
context.external = isExternal(dev);
|
||||||
@@ -487,7 +519,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
context.productId = dev.getProductId();
|
context.productId = dev.getProductId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev.getVibrator().hasVibrator()) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
|
||||||
|
context.vibratorManager = dev.getVibratorManager();
|
||||||
|
}
|
||||||
|
else if (dev.getVibrator().hasVibrator()) {
|
||||||
context.vibrator = dev.getVibrator();
|
context.vibrator = dev.getVibrator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,9 +707,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
// SHIELD controllers will use small stick deadzones
|
// SHIELD controllers will use small stick deadzones
|
||||||
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
|
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
|
||||||
context.leftStickDeadzoneRadius = 0.07f;
|
|
||||||
context.rightStickDeadzoneRadius = 0.07f;
|
|
||||||
|
|
||||||
// The big Nvidia button on the Shield controllers acts like a Search button. It
|
// The big Nvidia button on the Shield controllers acts like a Search button. It
|
||||||
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
|
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
|
||||||
// nothing, so we can hijack it to act like a mode button.
|
// nothing, so we can hijack it to act like a mode button.
|
||||||
@@ -1283,7 +1315,65 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
@TargetApi(31)
|
||||||
|
private boolean hasDualAmplitudeControlledRumbleVibrators(VibratorManager vm) {
|
||||||
|
int[] vibratorIds = vm.getVibratorIds();
|
||||||
|
|
||||||
|
// There must be exactly 2 vibrators on this device
|
||||||
|
if (vibratorIds.length != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both vibrators must have amplitude control
|
||||||
|
for (int vid : vibratorIds) {
|
||||||
|
if (!vm.getVibrator(vid).hasAmplitudeControl()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This must only be called if hasDualAmplitudeControlledRumbleVibrators() is true!
|
||||||
|
@TargetApi(31)
|
||||||
|
private void rumbleDualVibrators(VibratorManager vm, short lowFreqMotor, short highFreqMotor) {
|
||||||
|
// Normalize motor values to 0-255 amplitudes for VibrationManager
|
||||||
|
highFreqMotor = (short)((highFreqMotor >> 8) & 0xFF);
|
||||||
|
lowFreqMotor = (short)((lowFreqMotor >> 8) & 0xFF);
|
||||||
|
|
||||||
|
// If they're both zero, we can just call cancel().
|
||||||
|
if (lowFreqMotor == 0 && highFreqMotor == 0) {
|
||||||
|
vm.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no documentation that states that vibrators for FF_RUMBLE input devices will
|
||||||
|
// always be enumerated in this order, but it seems consistent between Xbox Series X (USB),
|
||||||
|
// PS3 (USB), and PS4 (USB+BT) controllers on Android 12 Beta 3.
|
||||||
|
int[] vibratorIds = vm.getVibratorIds();
|
||||||
|
int[] vibratorAmplitudes = new int[] { highFreqMotor, lowFreqMotor };
|
||||||
|
|
||||||
|
CombinedVibration.ParallelCombination combo = CombinedVibration.startParallel();
|
||||||
|
|
||||||
|
for (int i = 0; i < vibratorIds.length; i++) {
|
||||||
|
// It's illegal to create a VibrationEffect with an amplitude of 0.
|
||||||
|
// Simply excluding that vibrator from our ParallelCombination will turn it off.
|
||||||
|
if (vibratorAmplitudes[i] != 0) {
|
||||||
|
combo.addVibrator(vibratorIds[i], VibrationEffect.createOneShot(60000, vibratorAmplitudes[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.vibrate(combo.combine(), vibrationAttributes.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||||
// Since we can only use a single amplitude value, compute the desired amplitude
|
// Since we can only use a single amplitude value, compute the desired amplitude
|
||||||
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
|
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
|
||||||
// NB: This value is now 0-255 as required by VibrationEffect.
|
// NB: This value is now 0-255 as required by VibrationEffect.
|
||||||
@@ -1305,10 +1395,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (vibrator.hasAmplitudeControl()) {
|
if (vibrator.hasAmplitudeControl()) {
|
||||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
.setUsage(AudioAttributes.USAGE_GAME)
|
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
.build();
|
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||||
vibrator.vibrate(effect, audioAttributes);
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(effect, vibrationAttributes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_GAME)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(effect, audioAttributes);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1318,7 +1417,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
long pwmPeriod = 20;
|
long pwmPeriod = 20;
|
||||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||||
long offTime = pwmPeriod - onTime;
|
long offTime = pwmPeriod - onTime;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
|
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||||
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(VibrationEffect.createWaveform(new long[]{0, onTime, offTime}, 0), vibrationAttributes);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||||
.setUsage(AudioAttributes.USAGE_GAME)
|
.setUsage(AudioAttributes.USAGE_GAME)
|
||||||
.build();
|
.build();
|
||||||
@@ -1339,9 +1445,37 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
if (deviceContext.controllerNumber == controllerNumber) {
|
if (deviceContext.controllerNumber == controllerNumber) {
|
||||||
foundMatchingDevice = true;
|
foundMatchingDevice = true;
|
||||||
|
|
||||||
if (deviceContext.vibrator != null) {
|
// Cancel pending rumble repeat timer if one exists
|
||||||
|
if (deviceContext.rumbleRepeatTimer != null) {
|
||||||
|
deviceContext.rumbleRepeatTimer.cancel();
|
||||||
|
deviceContext.rumbleRepeatTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
||||||
vibrated = true;
|
vibrated = true;
|
||||||
rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
||||||
|
}
|
||||||
|
// On Shield devices, we can use their special API to rumble Shield controllers
|
||||||
|
else if (shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
||||||
|
vibrated = true;
|
||||||
|
|
||||||
|
// The Shield controller can only rumble up to 1 second at a time, so we will call rumble again
|
||||||
|
// every 500 ms until the host PC gives us another rumble value.
|
||||||
|
if (lowFreqMotor != 0 || highFreqMotor != 0) {
|
||||||
|
deviceContext.rumbleRepeatTimer = new Timer("Rumble Repeat - "+deviceContext.name, true);
|
||||||
|
deviceContext.rumbleRepeatTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor);
|
||||||
|
}
|
||||||
|
}, 500, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If all else fails, we have to try the old Vibrator API
|
||||||
|
else if (deviceContext.vibrator != null) {
|
||||||
|
vibrated = true;
|
||||||
|
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1361,12 +1495,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
// controls that triggered the rumble. Vibrate the device if
|
// controls that triggered the rumble. Vibrate the device if
|
||||||
// the user has requested that behavior.
|
// the user has requested that behavior.
|
||||||
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
|
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
|
||||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
|
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
|
||||||
// We found a device to vibrate but it didn't have rumble support. The user
|
// We found a device to vibrate but it didn't have rumble support. The user
|
||||||
// has requested us to vibrate the device in this case.
|
// has requested us to vibrate the device in this case.
|
||||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1396,7 +1530,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
// UI thread.
|
// UI thread.
|
||||||
try {
|
try {
|
||||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
@@ -1505,7 +1646,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1523,7 +1671,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1803,7 +1958,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
|
|
||||||
class InputDeviceContext extends GenericControllerContext {
|
class InputDeviceContext extends GenericControllerContext {
|
||||||
public String name;
|
public String name;
|
||||||
|
public VibratorManager vibratorManager;
|
||||||
public Vibrator vibrator;
|
public Vibrator vibrator;
|
||||||
|
public InputDevice inputDevice;
|
||||||
|
public Timer rumbleRepeatTimer;
|
||||||
|
|
||||||
public int leftStickXAxis = -1;
|
public int leftStickXAxis = -1;
|
||||||
public int leftStickYAxis = -1;
|
public int leftStickYAxis = -1;
|
||||||
@@ -1849,9 +2007,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
public void destroy() {
|
public void destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
|
||||||
if (vibrator != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && vibratorManager != null) {
|
||||||
|
vibratorManager.cancel();
|
||||||
|
}
|
||||||
|
else if (vibrator != null) {
|
||||||
vibrator.cancel();
|
vibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rumbleRepeatTimer != null) {
|
||||||
|
rumbleRepeatTimer.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package com.limelight.binding.input;
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to translate a Android key code into the codes GFE is expecting
|
* Class to translate a Android key code into the codes GFE is expecting
|
||||||
* @author Diego Waxemberg
|
* @author Diego Waxemberg
|
||||||
* @author Cameron Gutman
|
* @author Cameron Gutman
|
||||||
*/
|
*/
|
||||||
public class KeyboardTranslator {
|
public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GFE's prefix for every key code
|
* GFE's prefix for every key code
|
||||||
@@ -48,6 +55,55 @@ public class KeyboardTranslator {
|
|||||||
public static final int VK_QUOTE = 222;
|
public static final int VK_QUOTE = 222;
|
||||||
public static final int VK_PAUSE = 19;
|
public static final int VK_PAUSE = 19;
|
||||||
|
|
||||||
|
private static class KeyboardMapping {
|
||||||
|
private final InputDevice device;
|
||||||
|
private final int[] deviceKeyCodeToQwertyKeyCode;
|
||||||
|
|
||||||
|
@TargetApi(33)
|
||||||
|
public KeyboardMapping(InputDevice device) {
|
||||||
|
int maxKeyCode = KeyEvent.getMaxKeyCode();
|
||||||
|
|
||||||
|
this.device = device;
|
||||||
|
this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1];
|
||||||
|
|
||||||
|
// Any unmatched keycodes are treated as unknown
|
||||||
|
Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN);
|
||||||
|
|
||||||
|
for (int i = 0; i <= maxKeyCode; i++) {
|
||||||
|
int deviceKeyCode = device.getKeyCodeForKeyLocation(i);
|
||||||
|
if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(33)
|
||||||
|
public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) {
|
||||||
|
return device.getKeyCodeForKeyLocation(qwertyKeyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) {
|
||||||
|
if (deviceKeyCode > KeyEvent.getMaxKeyCode()) {
|
||||||
|
return KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceKeyCodeToQwertyKeyCode[deviceKeyCode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SparseArray<KeyboardMapping> keyboardMappings = new SparseArray<>();
|
||||||
|
|
||||||
|
public KeyboardTranslator() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.set(deviceId, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean needsShift(int keycode) {
|
public static boolean needsShift(int keycode) {
|
||||||
switch (keycode)
|
switch (keycode)
|
||||||
{
|
{
|
||||||
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
|
|||||||
/**
|
/**
|
||||||
* Translates the given keycode and returns the GFE keycode
|
* Translates the given keycode and returns the GFE keycode
|
||||||
* @param keycode the code to be translated
|
* @param keycode the code to be translated
|
||||||
|
* @param deviceId InputDevice.getId() or -1 if unknown
|
||||||
* @return a GFE keycode for the given keycode
|
* @return a GFE keycode for the given keycode
|
||||||
*/
|
*/
|
||||||
public static short translate(int keycode) {
|
public short translate(int keycode, int deviceId) {
|
||||||
int translated;
|
int translated;
|
||||||
|
|
||||||
|
// If a device ID was provided, look up the keyboard mapping
|
||||||
|
if (deviceId >= 0) {
|
||||||
|
KeyboardMapping mapping = keyboardMappings.get(deviceId);
|
||||||
|
if (mapping != null) {
|
||||||
|
// Try to map this device-specific keycode onto a QWERTY layout.
|
||||||
|
// GFE assumes incoming keycodes are from a QWERTY keyboard.
|
||||||
|
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
|
||||||
|
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
keycode = qwertyKeyCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a poor man's mapping between Android key codes
|
// This is a poor man's mapping between Android key codes
|
||||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||||
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
|
|||||||
return (short) ((KEY_PREFIX << 8) | translated);
|
return (short) ((KEY_PREFIX << 8) | translated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int index) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
InputDevice device = InputDevice.getDevice(index);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.put(index, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int index) {
|
||||||
|
keyboardMappings.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int index) {
|
||||||
|
keyboardMappings.remove(index);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
InputDevice device = InputDevice.getDevice(index);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.set(index, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+95
-4
@@ -2,7 +2,9 @@ package com.limelight.binding.input.capture;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -12,11 +14,13 @@ import android.view.View;
|
|||||||
// pointer icon hiding behavior over our stream view just in case pointer capture
|
// pointer icon hiding behavior over our stream view just in case pointer capture
|
||||||
// is unavailable on this system (ex: DeX, ChromeOS)
|
// is unavailable on this system (ex: DeX, ChromeOS)
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider {
|
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider implements InputManager.InputDeviceListener {
|
||||||
|
private InputManager inputManager;
|
||||||
private View targetView;
|
private View targetView;
|
||||||
|
|
||||||
public AndroidNativePointerCaptureProvider(Activity activity, View targetView) {
|
public AndroidNativePointerCaptureProvider(Activity activity, View targetView) {
|
||||||
super(activity, targetView);
|
super(activity, targetView);
|
||||||
|
this.inputManager = activity.getSystemService(InputManager.class);
|
||||||
this.targetView = targetView;
|
this.targetView = targetView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,25 +28,85 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
|||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only capture the pointer if we have a compatible InputDevice
|
||||||
|
// present. This is a workaround for an Android 12 regression causing
|
||||||
|
// incorrect mouse input when using the SPen.
|
||||||
|
// https://github.com/moonlight-stream/moonlight-android/issues/1030
|
||||||
|
private boolean hasCaptureCompatibleInputDevice() {
|
||||||
|
for (int id : InputDevice.getDeviceIds()) {
|
||||||
|
InputDevice device = InputDevice.getDevice(id);
|
||||||
|
if (device == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip touchscreens when considering compatible capture devices.
|
||||||
|
// Samsung devices on Android 12 will report a sec_touchpad device
|
||||||
|
// with SOURCE_TOUCHSCREEN, SOURCE_KEYBOARD, and SOURCE_MOUSE.
|
||||||
|
// Upon enabling pointer capture, that device will switch to
|
||||||
|
// SOURCE_KEYBOARD and SOURCE_TOUCHPAD.
|
||||||
|
if (device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.supportsSource(InputDevice.SOURCE_MOUSE) ||
|
||||||
|
device.supportsSource(InputDevice.SOURCE_MOUSE_RELATIVE) ||
|
||||||
|
device.supportsSource(InputDevice.SOURCE_TOUCHPAD)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
super.enableCapture();
|
super.enableCapture();
|
||||||
targetView.requestPointerCapture();
|
|
||||||
|
// Listen for device events to enable/disable capture
|
||||||
|
inputManager.registerInputDeviceListener(this, null);
|
||||||
|
|
||||||
|
// Capture now if we have a capture-capable device
|
||||||
|
if (hasCaptureCompatibleInputDevice()) {
|
||||||
|
targetView.requestPointerCapture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
super.disableCapture();
|
super.disableCapture();
|
||||||
|
inputManager.unregisterInputDeviceListener(this);
|
||||||
targetView.releasePointerCapture();
|
targetView.releasePointerCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean focusActive) {
|
||||||
|
if (!focusActive || !isCapturing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recapture the pointer if focus was regained. On Android Q,
|
||||||
|
// we have to delay a bit before requesting capture because otherwise
|
||||||
|
// we'll hit the "requestPointerCapture called for a window that has no focus"
|
||||||
|
// error and it will not actually capture the cursor.
|
||||||
|
Handler h = new Handler();
|
||||||
|
h.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (hasCaptureCompatibleInputDevice()) {
|
||||||
|
targetView.requestPointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
|
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
|
||||||
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
|
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
|
||||||
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
|
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
|
||||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
int eventSource = event.getSource();
|
||||||
(event.getSource() == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
return (eventSource == InputDevice.SOURCE_MOUSE_RELATIVE && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) ||
|
||||||
|
(eventSource == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -66,4 +130,31 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
|||||||
}
|
}
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int deviceId) {
|
||||||
|
// Check if we've added a capture-compatible device
|
||||||
|
if (!targetView.hasPointerCapture() && hasCaptureCompatibleInputDevice()) {
|
||||||
|
targetView.requestPointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
|
// Check if the capture-compatible device was removed
|
||||||
|
if (targetView.hasPointerCapture() && !hasCaptureCompatibleInputDevice()) {
|
||||||
|
targetView.releasePointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int deviceId) {
|
||||||
|
// Emulating a remove+add should be sufficient for our purposes.
|
||||||
|
//
|
||||||
|
// Note: This callback must be handled carefully because it can happen as a result of
|
||||||
|
// calling requestPointerCapture(). This can cause trackpad devices to gain SOURCE_MOUSE_RELATIVE
|
||||||
|
// and re-enter this callback.
|
||||||
|
onInputDeviceRemoved(deviceId);
|
||||||
|
onInputDeviceAdded(deviceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ package com.limelight.binding.input.capture;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import com.limelight.BuildConfig;
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.LimelightBuildProps;
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
@@ -16,7 +16,7 @@ public class InputCaptureManager {
|
|||||||
}
|
}
|
||||||
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
||||||
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
||||||
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
else if (!BuildConfig.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||||
return new ShieldCaptureProvider(activity);
|
return new ShieldCaptureProvider(activity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,4 +33,6 @@ public abstract class InputCaptureProvider {
|
|||||||
public float getRelativeAxisY(MotionEvent event) {
|
public float getRelativeAxisY(MotionEvent event) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onWindowFocusChanged(boolean focusActive) {}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,9 @@ public abstract class AbstractXboxController extends AbstractController {
|
|||||||
// around when we call notifyDeviceAdded(), we won't be able to claim
|
// around when we call notifyDeviceAdded(), we won't be able to claim
|
||||||
// the controller number used by the original InputDevice.
|
// the controller number used by the original InputDevice.
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {}
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Report that we're added _before_ reporting input
|
// Report that we're added _before_ reporting input
|
||||||
notifyDeviceAdded();
|
notifyDeviceAdded();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
private UsbManager usbManager;
|
private UsbManager usbManager;
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
|
private boolean started;
|
||||||
|
|
||||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||||
@@ -36,6 +37,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
private UsbDriverListener listener;
|
private UsbDriverListener listener;
|
||||||
|
private UsbDriverStateListener stateListener;
|
||||||
private int nextDeviceId;
|
private int nextDeviceId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
|
||||||
|
// Permission dialog is now closed
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
// If we got this far, we've already found we're able to handle this 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)) {
|
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||||
handleUsbDeviceState(device);
|
handleUsbDeviceState(device);
|
||||||
@@ -112,6 +119,18 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStateListener(UsbDriverStateListener stateListener) {
|
||||||
|
UsbDriverService.this.stateListener = stateListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
UsbDriverService.this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
UsbDriverService.this.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUsbDeviceState(UsbDevice device) {
|
private void handleUsbDeviceState(UsbDevice device) {
|
||||||
@@ -121,15 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
if (!usbManager.hasPermission(device)) {
|
if (!usbManager.hasPermission(device)) {
|
||||||
// Let's ask for permission
|
// Let's ask for permission
|
||||||
try {
|
try {
|
||||||
|
// Tell the state listener that we're about to display a permission dialog
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptStarting();
|
||||||
|
}
|
||||||
|
|
||||||
|
int intentFlags = 0;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
// This function is not documented as throwing any exceptions (denying access
|
// This function is not documented as throwing any exceptions (denying access
|
||||||
// is indicated by calling the PendingIntent with a false result). However,
|
// is indicated by calling the PendingIntent with a false result). However,
|
||||||
// Samsung Knox has some policies which block this request, but rather than
|
// Samsung Knox has some policies which block this request, but rather than
|
||||||
// just returning a false result or returning 0 enumerated devices,
|
// just returning a false result or returning 0 enumerated devices,
|
||||||
// they throw an undocumented SecurityException from this call, crashing
|
// they throw an undocumented SecurityException from this call, crashing
|
||||||
// the whole app. :(
|
// the whole app. :(
|
||||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
|
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void start() {
|
||||||
public void onCreate() {
|
if (started) {
|
||||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
return;
|
||||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
}
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
|
||||||
// Register for USB attach broadcasts and permission completions
|
// Register for USB attach broadcasts and permission completions
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||||
filter.addAction(ACTION_USB_PERMISSION);
|
filter.addAction(ACTION_USB_PERMISSION);
|
||||||
registerReceiver(receiver, filter);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
registerReceiver(receiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
// Enumerate existing devices
|
// Enumerate existing devices
|
||||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||||
@@ -240,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void stop() {
|
||||||
public void onDestroy() {
|
if (!started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
|
||||||
// Stop the attachment receiver
|
// Stop the attachment receiver
|
||||||
unregisterReceiver(receiver);
|
unregisterReceiver(receiver);
|
||||||
|
|
||||||
// Remove listeners
|
|
||||||
listener = null;
|
|
||||||
|
|
||||||
// Stop all controllers
|
// Stop all controllers
|
||||||
while (controllers.size() > 0) {
|
while (controllers.size() > 0) {
|
||||||
// Stop and remove the controller
|
// Stop and remove the controller
|
||||||
@@ -255,8 +297,28 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
// Remove listeners
|
||||||
|
listener = null;
|
||||||
|
stateListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface UsbDriverStateListener {
|
||||||
|
void onUsbPermissionPromptStarting();
|
||||||
|
void onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ public class Xbox360Controller extends AbstractXboxController {
|
|||||||
0x0f0d, // Hori
|
0x0f0d, // Hori
|
||||||
0x1038, // SteelSeries
|
0x1038, // SteelSeries
|
||||||
0x11c9, // Nacon
|
0x11c9, // Nacon
|
||||||
|
0x1209, // Ardwiino
|
||||||
0x12ab, // Unknown
|
0x12ab, // Unknown
|
||||||
0x1430, // RedOctane
|
0x1430, // RedOctane
|
||||||
0x146b, // BigBen
|
0x146b, // BigBen
|
||||||
@@ -33,8 +34,11 @@ public class Xbox360Controller extends AbstractXboxController {
|
|||||||
0x15e4, // Numark
|
0x15e4, // Numark
|
||||||
0x162e, // Joytech
|
0x162e, // Joytech
|
||||||
0x1689, // Razer Onza
|
0x1689, // Razer Onza
|
||||||
|
0x1949, // Lab126 (Amazon Luna)
|
||||||
0x1bad, // Harmonix
|
0x1bad, // Harmonix
|
||||||
|
0x20d6, // PowerA
|
||||||
0x24c6, // PowerA
|
0x24c6, // PowerA
|
||||||
|
0x2f24, // GameSir
|
||||||
};
|
};
|
||||||
|
|
||||||
public static boolean canClaimDevice(UsbDevice device) {
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
0x0e6f, // Unknown
|
0x0e6f, // Unknown
|
||||||
0x0f0d, // Hori
|
0x0f0d, // Hori
|
||||||
0x1532, // Razer Wildcat
|
0x1532, // Razer Wildcat
|
||||||
|
0x20d6, // PowerA
|
||||||
0x24c6, // PowerA
|
0x24c6, // PowerA
|
||||||
|
0x2e24, // Hyperkin
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import com.limelight.LimelightBuildProps;
|
import com.limelight.BuildConfig;
|
||||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||||
|
|
||||||
public class EvdevCaptureProviderShim {
|
public class EvdevCaptureProviderShim {
|
||||||
public static boolean isCaptureProviderSupported() {
|
public static boolean isCaptureProviderSupported() {
|
||||||
return LimelightBuildProps.ROOT_BUILD;
|
return BuildConfig.ROOT_BUILD;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package com.limelight.binding.input.shield;
|
||||||
|
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
public interface IExposedControllerManagerListener extends IInterface {
|
||||||
|
void onDeviceAdded(String controllerToken);
|
||||||
|
void onDeviceChanged(String controllerToken, int i);
|
||||||
|
void onDeviceRemoved(String controllerToken);
|
||||||
|
|
||||||
|
public static abstract class Stub extends Binder implements IExposedControllerManagerListener {
|
||||||
|
public Stub() {
|
||||||
|
attachInterface(this, "com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onTransact(int code, Parcel input, Parcel output, int flags) throws RemoteException {
|
||||||
|
switch (code) {
|
||||||
|
case 1:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceAdded(input.readString());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceChanged(input.readString(), input.readInt());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceRemoved(input.readString());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
// Don't care
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onTransact(code, input, output, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+291
@@ -0,0 +1,291 @@
|
|||||||
|
package com.limelight.binding.input.shield;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class ShieldControllerExtensionsHandler implements InputManager.InputDeviceListener {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private IBinder binder;
|
||||||
|
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
|
binder = iBinder;
|
||||||
|
|
||||||
|
try {
|
||||||
|
listenerId = registerListener();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
listenerId = 0;
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
binder = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ConcurrentHashMap handles synchronization between the Binder thread adding/removing
|
||||||
|
// entries and callers on arbitrary threads that are doing device lookups.
|
||||||
|
//
|
||||||
|
// Since these are separate maps, they can be temporarily inconsistent (only one-way
|
||||||
|
// of the two-way mapping present). This is fine for our purposes here.
|
||||||
|
private ConcurrentHashMap<String, Integer> tokenToDeviceIdMap = new ConcurrentHashMap<>();
|
||||||
|
private ConcurrentHashMap<Integer, String> deviceIdToTokenMap = new ConcurrentHashMap<>();
|
||||||
|
private AtomicBoolean needsRefresh = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private int listenerId;
|
||||||
|
private IExposedControllerManagerListener.Stub controllerListener = new IExposedControllerManagerListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onDeviceAdded(String controllerToken) {
|
||||||
|
try {
|
||||||
|
int inputDeviceId = getInputDeviceId(controllerToken);
|
||||||
|
|
||||||
|
LimeLog.info("Shield controller added: " + controllerToken + " -> " + inputDeviceId);
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.put(controllerToken, inputDeviceId);
|
||||||
|
deviceIdToTokenMap.put(inputDeviceId, controllerToken);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceChanged(String controllerToken, int i) {
|
||||||
|
LimeLog.info("Shield controller changed: " + controllerToken + " " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceRemoved(String controllerToken) {
|
||||||
|
LimeLog.info("Shield controller removed: " + controllerToken);
|
||||||
|
|
||||||
|
Integer deviceId = tokenToDeviceIdMap.remove(controllerToken);
|
||||||
|
if (deviceId != null) {
|
||||||
|
deviceIdToTokenMap.remove(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public ShieldControllerExtensionsHandler(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
inputManager.registerInputDeviceListener(this, null);
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setClassName("com.nvidia.blakepairing", "com.nvidia.blakepairing.AccessoryService");
|
||||||
|
if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
|
||||||
|
LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getControllerToken(InputDevice device) {
|
||||||
|
// Refresh device ID <-> token mappings if one of our devices was removed
|
||||||
|
if (needsRefresh.compareAndSet(true, false)) {
|
||||||
|
try {
|
||||||
|
LimeLog.info("Refreshing controller token mappings");
|
||||||
|
|
||||||
|
// We have to enumerate tokenToDeviceIdMap rather than deviceIdToTokenMap
|
||||||
|
// because we remove the deviceIdToTokenMap entry when the device goes away.
|
||||||
|
HashMap<String, Integer> newTokenToDeviceIdMap = new HashMap<>();
|
||||||
|
HashMap<Integer, String> newDeviceIdToTokenMap = new HashMap<>();
|
||||||
|
for (String existingToken : tokenToDeviceIdMap.keySet()) {
|
||||||
|
int deviceId = getInputDeviceId(existingToken);
|
||||||
|
if (deviceId != 0) {
|
||||||
|
newTokenToDeviceIdMap.put(existingToken, deviceId);
|
||||||
|
newDeviceIdToTokenMap.put(deviceId, existingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.putAll(newTokenToDeviceIdMap);
|
||||||
|
deviceIdToTokenMap.putAll(newDeviceIdToTokenMap);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceIdToTokenMap.get(device.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean rumble(InputDevice device, int lowFreqMotor, int highFreqMotor) {
|
||||||
|
String controllerToken = getControllerToken(device);
|
||||||
|
if (controllerToken != null) {
|
||||||
|
try {
|
||||||
|
return rumble(controllerToken, lowFreqMotor, highFreqMotor);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
inputManager.unregisterInputDeviceListener(this);
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
if (listenerId != 0) {
|
||||||
|
try {
|
||||||
|
unregisterListener(listenerId);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
listenerId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binder != null) {
|
||||||
|
context.unbindService(serviceConnection);
|
||||||
|
binder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int registerListener() throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeStrongBinder(controllerListener);
|
||||||
|
|
||||||
|
binder.transact(20, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt();
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unregisterListener(int listenerId) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeInt(listenerId);
|
||||||
|
|
||||||
|
binder.transact(21, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInputDeviceId(String controllerToken) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
|
||||||
|
binder.transact(13, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt();
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rumble duration maximum of 1 second
|
||||||
|
private boolean rumble(String controllerToken, int lowFreqMotor, int highFreqMotor) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
input.writeInt(lowFreqMotor);
|
||||||
|
input.writeInt(highFreqMotor);
|
||||||
|
|
||||||
|
binder.transact(18, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rumble duration maximum of 1.5 seconds
|
||||||
|
private boolean rumbleWithDuration(String controllerToken, int lowFreqMotor, int highFreqMotor, long durationMs) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
input.writeInt(lowFreqMotor);
|
||||||
|
input.writeInt(highFreqMotor);
|
||||||
|
input.writeLong(durationMs);
|
||||||
|
|
||||||
|
binder.transact(19, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int deviceId) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int deviceId) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
|
// Remove the device ID to token mapping, but leave the token mapping to device ID
|
||||||
|
// mapping so we will re-enumerate it when we next try to rumble a controller.
|
||||||
|
if (deviceIdToTokenMap.remove(deviceId) != null) {
|
||||||
|
needsRefresh.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,12 @@ public class AbsoluteTouchContext implements TouchContext {
|
|||||||
private boolean confirmedTap;
|
private boolean confirmedTap;
|
||||||
private Timer longPressTimer;
|
private Timer longPressTimer;
|
||||||
private Timer tapDownTimer;
|
private Timer tapDownTimer;
|
||||||
private float accumulatedScrollDelta;
|
|
||||||
|
|
||||||
private final NvConnection conn;
|
private final NvConnection conn;
|
||||||
private final int actionIndex;
|
private final int actionIndex;
|
||||||
private final View targetView;
|
private final View targetView;
|
||||||
|
|
||||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
private static final int SCROLL_SPEED_FACTOR = 3;
|
||||||
|
|
||||||
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
|
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
|
||||||
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
|
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
|
||||||
@@ -65,7 +64,6 @@ public class AbsoluteTouchContext implements TouchContext {
|
|||||||
lastTouchLocationY = lastTouchDownY = eventY;
|
lastTouchLocationY = lastTouchDownY = eventY;
|
||||||
lastTouchDownTime = SystemClock.uptimeMillis();
|
lastTouchDownTime = SystemClock.uptimeMillis();
|
||||||
cancelled = confirmedTap = confirmedLongPress = false;
|
cancelled = confirmedTap = confirmedLongPress = false;
|
||||||
accumulatedScrollDelta = 0;
|
|
||||||
|
|
||||||
if (actionIndex == 0) {
|
if (actionIndex == 0) {
|
||||||
// Start the timers
|
// Start the timers
|
||||||
@@ -118,7 +116,14 @@ public class AbsoluteTouchContext implements TouchContext {
|
|||||||
try {
|
try {
|
||||||
// FIXME: Sleeping on the main thread sucks
|
// FIXME: Sleeping on the main thread sucks
|
||||||
Thread.sleep(50);
|
Thread.sleep(50);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,11 +233,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (actionIndex == 1) {
|
else if (actionIndex == 1) {
|
||||||
accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
|
conn.sendMouseHighResScroll((short)((eventY - lastTouchLocationY) * SCROLL_SPEED_FACTOR));
|
||||||
if ((short)accumulatedScrollDelta != 0) {
|
|
||||||
conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
|
|
||||||
accumulatedScrollDelta -= (short)accumulatedScrollDelta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTouchLocationX = eventX;
|
lastTouchLocationX = eventX;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@@ -30,22 +31,25 @@ public class RelativeTouchContext implements TouchContext {
|
|||||||
private final int referenceWidth;
|
private final int referenceWidth;
|
||||||
private final int referenceHeight;
|
private final int referenceHeight;
|
||||||
private final View targetView;
|
private final View targetView;
|
||||||
|
private final PreferenceConfiguration prefConfig;
|
||||||
|
|
||||||
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
||||||
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
||||||
private static final int TAP_TIME_THRESHOLD = 250;
|
private static final int TAP_TIME_THRESHOLD = 250;
|
||||||
private static final int DRAG_TIME_THRESHOLD = 650;
|
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||||
|
|
||||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
private static final int SCROLL_SPEED_FACTOR = 5;
|
||||||
|
|
||||||
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||||
int referenceWidth, int referenceHeight, View view)
|
int referenceWidth, int referenceHeight,
|
||||||
|
View view, PreferenceConfiguration prefConfig)
|
||||||
{
|
{
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
this.actionIndex = actionIndex;
|
this.actionIndex = actionIndex;
|
||||||
this.referenceWidth = referenceWidth;
|
this.referenceWidth = referenceWidth;
|
||||||
this.referenceHeight = referenceHeight;
|
this.referenceHeight = referenceHeight;
|
||||||
this.targetView = view;
|
this.targetView = view;
|
||||||
|
this.prefConfig = prefConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -139,7 +143,14 @@ public class RelativeTouchContext implements TouchContext {
|
|||||||
// do input detection by polling
|
// do input detection by polling
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
// Raise the mouse button
|
// Raise the mouse button
|
||||||
conn.sendMouseButtonUp(buttonIndex);
|
conn.sendMouseButtonUp(buttonIndex);
|
||||||
@@ -147,6 +158,9 @@ public class RelativeTouchContext implements TouchContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void startDragTimer() {
|
private synchronized void startDragTimer() {
|
||||||
|
// Cancel any existing drag timers
|
||||||
|
cancelDragTimer();
|
||||||
|
|
||||||
dragTimer = new Timer(true);
|
dragTimer = new Timer(true);
|
||||||
dragTimer.schedule(new TimerTask() {
|
dragTimer.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
@@ -157,6 +171,11 @@ public class RelativeTouchContext implements TouchContext {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The drag should only be processed for the primary finger
|
||||||
|
if (actionIndex != maxPointerCountInGesture - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if someone cancelled us
|
// Check if someone cancelled us
|
||||||
if (dragTimer == null) {
|
if (dragTimer == null) {
|
||||||
return;
|
return;
|
||||||
@@ -240,12 +259,19 @@ public class RelativeTouchContext implements TouchContext {
|
|||||||
|
|
||||||
if (pointerCount == 2) {
|
if (pointerCount == 2) {
|
||||||
if (confirmedScroll) {
|
if (confirmedScroll) {
|
||||||
deltaY /= SCROLL_SPEED_DIVISOR;
|
conn.sendMouseHighResScroll((short)(deltaY * SCROLL_SPEED_FACTOR));
|
||||||
|
|
||||||
conn.sendMouseHighResScroll((short) deltaY);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
if (prefConfig.absoluteMouseMode) {
|
||||||
|
conn.sendMouseMoveAsMousePosition(
|
||||||
|
(short) deltaX,
|
||||||
|
(short) deltaY,
|
||||||
|
(short) targetView.getWidth(),
|
||||||
|
(short) targetView.getHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||||
|
|||||||
@@ -177,6 +177,15 @@ public class DigitalButton extends VirtualControllerElement {
|
|||||||
listener.onClick();
|
listener.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timerLongClick != null) {
|
||||||
|
timerLongClick.cancel();
|
||||||
|
timerLongClick = null;
|
||||||
|
}
|
||||||
|
if (longClickTimerTask != null) {
|
||||||
|
longClickTimerTask.cancel();
|
||||||
|
longClickTimerTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
timerLongClick = new Timer();
|
timerLongClick = new Timer();
|
||||||
longClickTimerTask = new TimerLongClickTimerTask();
|
longClickTimerTask = new TimerLongClickTimerTask();
|
||||||
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
|
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
|
||||||
@@ -200,9 +209,11 @@ public class DigitalButton extends VirtualControllerElement {
|
|||||||
// We may be called for a release without a prior click
|
// We may be called for a release without a prior click
|
||||||
if (timerLongClick != null) {
|
if (timerLongClick != null) {
|
||||||
timerLongClick.cancel();
|
timerLongClick.cancel();
|
||||||
|
timerLongClick = null;
|
||||||
}
|
}
|
||||||
if (longClickTimerTask != null) {
|
if (longClickTimerTask != null) {
|
||||||
longClickTimerTask.cancel();
|
longClickTimerTask.cancel();
|
||||||
|
longClickTimerTask = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-21
@@ -9,12 +9,11 @@ import android.util.DisplayMetrics;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.binding.input.ControllerHandler;
|
import com.limelight.binding.input.ControllerHandler;
|
||||||
import com.limelight.nvstream.NvConnection;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,11 +39,10 @@ public class VirtualController {
|
|||||||
|
|
||||||
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
||||||
|
|
||||||
private ControllerHandler controllerHandler;
|
private final ControllerHandler controllerHandler;
|
||||||
private Context context = null;
|
private final Context context;
|
||||||
|
|
||||||
private FrameLayout frame_layout = null;
|
private FrameLayout frame_layout = null;
|
||||||
private RelativeLayout relative_layout = null;
|
|
||||||
|
|
||||||
private Timer retransmitTimer;
|
private Timer retransmitTimer;
|
||||||
|
|
||||||
@@ -60,10 +58,6 @@ public class VirtualController {
|
|||||||
this.frame_layout = layout;
|
this.frame_layout = layout;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
relative_layout = new RelativeLayout(context);
|
|
||||||
|
|
||||||
frame_layout.addView(relative_layout);
|
|
||||||
|
|
||||||
buttonConfigure = new Button(context);
|
buttonConfigure = new Button(context);
|
||||||
buttonConfigure.setAlpha(0.25f);
|
buttonConfigure.setAlpha(0.25f);
|
||||||
buttonConfigure.setFocusable(false);
|
buttonConfigure.setFocusable(false);
|
||||||
@@ -87,7 +81,7 @@ public class VirtualController {
|
|||||||
|
|
||||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
relative_layout.invalidate();
|
buttonConfigure.invalidate();
|
||||||
|
|
||||||
for (VirtualControllerElement element : elements) {
|
for (VirtualControllerElement element : elements) {
|
||||||
element.invalidate();
|
element.invalidate();
|
||||||
@@ -99,11 +93,20 @@ public class VirtualController {
|
|||||||
|
|
||||||
public void hide() {
|
public void hide() {
|
||||||
retransmitTimer.cancel();
|
retransmitTimer.cancel();
|
||||||
relative_layout.setVisibility(View.INVISIBLE);
|
|
||||||
|
for (VirtualControllerElement element : elements) {
|
||||||
|
element.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonConfigure.setVisibility(View.INVISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show() {
|
public void show() {
|
||||||
relative_layout.setVisibility(View.VISIBLE);
|
for (VirtualControllerElement element : elements) {
|
||||||
|
element.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonConfigure.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// HACK: GFE sometimes discards gamepad packets when they are received
|
// HACK: GFE sometimes discards gamepad packets when they are received
|
||||||
// very shortly after another. This can be critical if an axis zeroing packet
|
// very shortly after another. This can be critical if an axis zeroing packet
|
||||||
@@ -120,9 +123,11 @@ public class VirtualController {
|
|||||||
|
|
||||||
public void removeElements() {
|
public void removeElements() {
|
||||||
for (VirtualControllerElement element : elements) {
|
for (VirtualControllerElement element : elements) {
|
||||||
relative_layout.removeView(element);
|
frame_layout.removeView(element);
|
||||||
}
|
}
|
||||||
elements.clear();
|
elements.clear();
|
||||||
|
|
||||||
|
frame_layout.removeView(buttonConfigure);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOpacity(int opacity) {
|
public void setOpacity(int opacity) {
|
||||||
@@ -134,10 +139,10 @@ public class VirtualController {
|
|||||||
|
|
||||||
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
|
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
|
||||||
elements.add(element);
|
elements.add(element);
|
||||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
|
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
|
||||||
layoutParams.setMargins(x, y, 0, 0);
|
layoutParams.setMargins(x, y, 0, 0);
|
||||||
|
|
||||||
relative_layout.addView(element, layoutParams);
|
frame_layout.addView(element, layoutParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<VirtualControllerElement> getElements() {
|
public List<VirtualControllerElement> getElements() {
|
||||||
@@ -146,23 +151,20 @@ public class VirtualController {
|
|||||||
|
|
||||||
private static final void _DBG(String text) {
|
private static final void _DBG(String text) {
|
||||||
if (_PRINT_DEBUG_INFORMATION) {
|
if (_PRINT_DEBUG_INFORMATION) {
|
||||||
System.out.println("VirtualController: " + text);
|
LimeLog.info("VirtualController: " + text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshLayout() {
|
public void refreshLayout() {
|
||||||
relative_layout.removeAllViews();
|
|
||||||
removeElements();
|
removeElements();
|
||||||
|
|
||||||
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
||||||
|
|
||||||
int buttonSize = (int)(screen.heightPixels*0.06f);
|
int buttonSize = (int)(screen.heightPixels*0.06f);
|
||||||
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize);
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
|
|
||||||
params.leftMargin = 15;
|
params.leftMargin = 15;
|
||||||
params.topMargin = 15;
|
params.topMargin = 15;
|
||||||
relative_layout.addView(buttonConfigure, params);
|
frame_layout.addView(buttonConfigure, params);
|
||||||
|
|
||||||
// Start with the default layout
|
// Start with the default layout
|
||||||
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
|
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
|
||||||
|
|||||||
+5
-5
@@ -12,7 +12,7 @@ import android.graphics.Paint;
|
|||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -72,7 +72,7 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
int newPos_x = (int) getX() + x - pressed_x;
|
int newPos_x = (int) getX() + x - pressed_x;
|
||||||
int newPos_y = (int) getY() + y - pressed_y;
|
int newPos_y = (int) getY() + y - pressed_y;
|
||||||
|
|
||||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
|
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
|
||||||
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
|
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
|
||||||
@@ -83,7 +83,7 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
|
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
|
||||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
int newHeight = height + (startSize_y - pressed_y);
|
int newHeight = height + (startSize_y - pressed_y);
|
||||||
int newWidth = width + (startSize_x - pressed_x);
|
int newWidth = width + (startSize_x - pressed_x);
|
||||||
@@ -316,7 +316,7 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
public JSONObject getConfiguration() throws JSONException {
|
public JSONObject getConfiguration() throws JSONException {
|
||||||
JSONObject configuration = new JSONObject();
|
JSONObject configuration = new JSONObject();
|
||||||
|
|
||||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
configuration.put("LEFT", layoutParams.leftMargin);
|
configuration.put("LEFT", layoutParams.leftMargin);
|
||||||
configuration.put("TOP", layoutParams.topMargin);
|
configuration.put("TOP", layoutParams.topMargin);
|
||||||
@@ -327,7 +327,7 @@ public abstract class VirtualControllerElement extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadConfiguration(JSONObject configuration) throws JSONException {
|
public void loadConfiguration(JSONObject configuration) throws JSONException {
|
||||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
layoutParams.leftMargin = configuration.getInt("LEFT");
|
layoutParams.leftMargin = configuration.getInt("LEFT");
|
||||||
layoutParams.topMargin = configuration.getInt("TOP");
|
layoutParams.topMargin = configuration.getInt("TOP");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.limelight.binding.video;
|
package com.limelight.binding.video;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
import org.jcodec.codecs.h264.H264Utils;
|
import org.jcodec.codecs.h264.H264Utils;
|
||||||
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
||||||
@@ -20,11 +22,15 @@ import android.media.MediaFormat;
|
|||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
import android.media.MediaCodec.CodecException;
|
import android.media.MediaCodec.CodecException;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
|
import android.view.Choreographer;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements Choreographer.FrameCallback {
|
||||||
|
|
||||||
private static final boolean USE_FRAME_RENDER_TIME = false;
|
private static final boolean USE_FRAME_RENDER_TIME = false;
|
||||||
private static final boolean FRAME_RENDER_TIME_ONLY = USE_FRAME_RENDER_TIME && false;
|
private static final boolean FRAME_RENDER_TIME_ONLY = USE_FRAME_RENDER_TIME && false;
|
||||||
@@ -45,8 +51,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private MediaCodec videoDecoder;
|
private MediaCodec videoDecoder;
|
||||||
private Thread rendererThread;
|
private Thread rendererThread;
|
||||||
private boolean needsSpsBitstreamFixup, isExynos4;
|
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||||
private boolean adaptivePlayback, directSubmit;
|
private boolean adaptivePlayback, directSubmit, fusedIdrFrame;
|
||||||
private boolean lowLatency;
|
|
||||||
private boolean constrainedHighProfile;
|
private boolean constrainedHighProfile;
|
||||||
private boolean refFrameInvalidationAvc, refFrameInvalidationHevc;
|
private boolean refFrameInvalidationAvc, refFrameInvalidationHevc;
|
||||||
private boolean refFrameInvalidationActive;
|
private boolean refFrameInvalidationActive;
|
||||||
@@ -59,7 +64,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private int consecutiveCrashCount;
|
private int consecutiveCrashCount;
|
||||||
private String glRenderer;
|
private String glRenderer;
|
||||||
private boolean foreground = true;
|
private boolean foreground = true;
|
||||||
private boolean legacyFrameDropRendering = false;
|
|
||||||
private PerfOverlayListener perfListener;
|
private PerfOverlayListener perfListener;
|
||||||
|
|
||||||
private MediaFormat inputFormat;
|
private MediaFormat inputFormat;
|
||||||
@@ -82,9 +86,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private int refreshRate;
|
private int refreshRate;
|
||||||
private PreferenceConfiguration prefs;
|
private PreferenceConfiguration prefs;
|
||||||
|
|
||||||
|
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
|
||||||
|
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
|
||||||
|
private long lastRenderedFrameTimeNanos;
|
||||||
|
private HandlerThread choreographerHandlerThread;
|
||||||
|
private Handler choreographerHandler;
|
||||||
|
|
||||||
private int numSpsIn;
|
private int numSpsIn;
|
||||||
private int numPpsIn;
|
private int numPpsIn;
|
||||||
private int numVpsIn;
|
private int numVpsIn;
|
||||||
|
private int numFramesIn;
|
||||||
|
private int numFramesOut;
|
||||||
|
|
||||||
private MediaCodecInfo findAvcDecoder() {
|
private MediaCodecInfo findAvcDecoder() {
|
||||||
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
||||||
@@ -94,8 +106,63 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
|
||||||
|
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
|
||||||
|
if (perfPoints != null) {
|
||||||
|
for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) {
|
||||||
|
// If we find a performance point that covers our target, we're good to go
|
||||||
|
if (perfPoint.covers(targetPerfPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We had performance point data but none met the specified streaming settings
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-through to try the Android M API if there's no performance point data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
try {
|
||||||
|
// We'll ask the decoder what it can do for us at this resolution and see if our
|
||||||
|
// requested frame rate falls below or inside the range of achievable frame rates.
|
||||||
|
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
|
||||||
|
return prefs.fps <= fpsRange.getUpper();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Video size not supported at any frame rate
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
|
||||||
|
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
|
||||||
|
|
||||||
|
Boolean avcCanMeetPP = decoderCanMeetPerformancePoint(avcCaps, prefs);
|
||||||
|
Boolean hevcCanMeetPP = decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
||||||
|
|
||||||
|
// If one or both codecs lack performance data, don't do anything
|
||||||
|
if (avcCanMeetPP == null || hevcCanMeetPP == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !avcCanMeetPP && hevcCanMeetPP;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No performance data
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
||||||
// Don't return anything if H.265 is forced off
|
// Don't return anything if HEVC is forced off
|
||||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -105,16 +172,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
|
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
|
||||||
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
|
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
|
||||||
// for even required levels of HEVC.
|
// for even required levels of HEVC.
|
||||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
MediaCodecInfo hevcDecoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||||
if (decoderInfo != null) {
|
if (hevcDecoderInfo != null) {
|
||||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
|
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(hevcDecoderInfo.getName(), meteredNetwork, prefs)) {
|
||||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+hevcDecoderInfo.getName());
|
||||||
|
|
||||||
|
// Force HEVC enabled if the user asked for it
|
||||||
|
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||||
|
LimeLog.info("Forcing HEVC enabled despite non-whitelisted decoder");
|
||||||
|
}
|
||||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
||||||
|
else if (requestedHdr) {
|
||||||
|
LimeLog.info("Forcing HEVC enabled for HDR streaming");
|
||||||
|
}
|
||||||
// > 4K streaming also requires HEVC, so force it on there too.
|
// > 4K streaming also requires HEVC, so force it on there too.
|
||||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
|
else if (prefs.width > 4096 || prefs.height > 4096) {
|
||||||
prefs.width > 4096 || prefs.height > 4096) {
|
LimeLog.info("Forcing HEVC enabled for over 4K streaming");
|
||||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
}
|
||||||
|
// Use HEVC if the H.264 decoder is unable to meet the performance point
|
||||||
|
else if (avcDecoder != null && decoderCanMeetPerformancePointWithHevcAndNotAvc(avcDecoder, hevcDecoderInfo, prefs)) {
|
||||||
|
LimeLog.info("Using non-whitelisted HEVC decoder to meet performance point");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return null;
|
||||||
@@ -122,7 +199,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoderInfo;
|
return hevcDecoderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRenderTarget(SurfaceHolder renderTarget) {
|
public void setRenderTarget(SurfaceHolder renderTarget) {
|
||||||
@@ -197,15 +274,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
return avcDecoder != null;
|
return avcDecoder != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBlacklistedForFrameRate(int frameRate) {
|
|
||||||
return avcDecoder != null && MediaCodecHelper.decoderBlacklistedForFrameRate(avcDecoder.getName(), frameRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableLegacyFrameDropRendering() {
|
|
||||||
LimeLog.info("Legacy frame drop rendering enabled");
|
|
||||||
legacyFrameDropRendering = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHevcMain10Hdr10Supported() {
|
public boolean isHevcMain10Hdr10Supported() {
|
||||||
if (hevcDecoder == null) {
|
if (hevcDecoder == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -233,125 +301,32 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
return this.videoFormat;
|
return this.videoFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private MediaFormat createBaseMediaFormat(String mimeType) {
|
||||||
public int setup(int format, int width, int height, int redrawRate) {
|
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, initialWidth, initialHeight);
|
||||||
this.initialWidth = width;
|
|
||||||
this.initialHeight = height;
|
|
||||||
this.videoFormat = format;
|
|
||||||
this.refreshRate = redrawRate;
|
|
||||||
|
|
||||||
String mimeType;
|
|
||||||
String selectedDecoderName;
|
|
||||||
|
|
||||||
if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H264) != 0) {
|
|
||||||
mimeType = "video/avc";
|
|
||||||
selectedDecoderName = avcDecoder.getName();
|
|
||||||
|
|
||||||
if (avcDecoder == null) {
|
|
||||||
LimeLog.severe("No available AVC decoder!");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width > 4096 || height > 4096) {
|
|
||||||
LimeLog.severe("> 4K streaming only supported on HEVC");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// These fixups only apply to H264 decoders
|
|
||||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderName);
|
|
||||||
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderName);
|
|
||||||
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(selectedDecoderName);
|
|
||||||
isExynos4 = MediaCodecHelper.isExynos4Device();
|
|
||||||
if (needsSpsBitstreamFixup) {
|
|
||||||
LimeLog.info("Decoder "+selectedDecoderName+" needs SPS bitstream restrictions fixup");
|
|
||||||
}
|
|
||||||
if (needsBaselineSpsHack) {
|
|
||||||
LimeLog.info("Decoder "+selectedDecoderName+" needs baseline SPS hack");
|
|
||||||
}
|
|
||||||
if (constrainedHighProfile) {
|
|
||||||
LimeLog.info("Decoder "+selectedDecoderName+" needs constrained high profile");
|
|
||||||
}
|
|
||||||
if (isExynos4) {
|
|
||||||
LimeLog.info("Decoder "+selectedDecoderName+" is on Exynos 4");
|
|
||||||
}
|
|
||||||
|
|
||||||
refFrameInvalidationActive = refFrameInvalidationAvc;
|
|
||||||
|
|
||||||
lowLatency = MediaCodecHelper.decoderSupportsLowLatency(avcDecoder, mimeType);
|
|
||||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(avcDecoder, mimeType);
|
|
||||||
}
|
|
||||||
else if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H265) != 0) {
|
|
||||||
mimeType = "video/hevc";
|
|
||||||
selectedDecoderName = hevcDecoder.getName();
|
|
||||||
|
|
||||||
if (hevcDecoder == null) {
|
|
||||||
LimeLog.severe("No available HEVC decoder!");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
refFrameInvalidationActive = refFrameInvalidationHevc;
|
|
||||||
|
|
||||||
lowLatency = MediaCodecHelper.decoderSupportsLowLatency(hevcDecoder, mimeType);
|
|
||||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(hevcDecoder, mimeType);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Unknown format
|
|
||||||
LimeLog.severe("Unknown format");
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
|
||||||
// due to implementation problems
|
|
||||||
try {
|
|
||||||
videoDecoder = MediaCodec.createByCodecName(selectedDecoderName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
|
|
||||||
|
|
||||||
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
|
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
// We use prefs.fps instead of redrawRate here because the low latency hack in Game.java
|
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, refreshRate);
|
||||||
// may leave us with an odd redrawRate value like 59 or 49 which might cause the decoder
|
|
||||||
// to puke. To be safe, we'll use the unmodified value.
|
|
||||||
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, prefs.fps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
||||||
// so we don't fill these pre-KitKat
|
// so we don't fill these pre-KitKat
|
||||||
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
|
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, initialWidth);
|
||||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lowLatency) {
|
return videoFormat;
|
||||||
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
}
|
||||||
}
|
|
||||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
// Set the Qualcomm vendor low latency extension if the Android R option is unavailable
|
|
||||||
if (MediaCodecHelper.decoderSupportsQcomVendorLowLatency(selectedDecoderName)) {
|
|
||||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
|
||||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
|
||||||
//
|
|
||||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
|
||||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
|
||||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(selectedDecoderName)) {
|
|
||||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configuredFormat = videoFormat;
|
|
||||||
LimeLog.info("Configuring with format: "+configuredFormat);
|
|
||||||
|
|
||||||
|
private boolean tryConfigureDecoder(MediaCodecInfo selectedDecoderInfo, MediaFormat format) {
|
||||||
try {
|
try {
|
||||||
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
|
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
|
||||||
|
LimeLog.info("Configuring with format: "+format);
|
||||||
|
|
||||||
|
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
|
||||||
|
|
||||||
|
configuredFormat = format;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
// This will contain the actual accepted input format attributes
|
// This will contain the actual accepted input format attributes
|
||||||
@@ -375,7 +350,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
|
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+format.getString(MediaFormat.KEY_MIME));
|
||||||
|
|
||||||
// Start the decoder
|
// Start the decoder
|
||||||
videoDecoder.start();
|
videoDecoder.start();
|
||||||
@@ -384,9 +359,100 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
legacyInputBuffers = videoDecoder.getInputBuffers();
|
legacyInputBuffers = videoDecoder.getInputBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return -5;
|
|
||||||
|
if (videoDecoder != null) {
|
||||||
|
videoDecoder.release();
|
||||||
|
videoDecoder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setup(int format, int width, int height, int redrawRate) {
|
||||||
|
this.initialWidth = width;
|
||||||
|
this.initialHeight = height;
|
||||||
|
this.videoFormat = format;
|
||||||
|
this.refreshRate = redrawRate;
|
||||||
|
|
||||||
|
String mimeType;
|
||||||
|
MediaCodecInfo selectedDecoderInfo;
|
||||||
|
|
||||||
|
if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H264) != 0) {
|
||||||
|
mimeType = "video/avc";
|
||||||
|
selectedDecoderInfo = avcDecoder;
|
||||||
|
|
||||||
|
if (avcDecoder == null) {
|
||||||
|
LimeLog.severe("No available AVC decoder!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > 4096 || height > 4096) {
|
||||||
|
LimeLog.severe("> 4K streaming only supported on HEVC");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These fixups only apply to H264 decoders
|
||||||
|
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderInfo.getName());
|
||||||
|
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderInfo.getName());
|
||||||
|
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(selectedDecoderInfo.getName());
|
||||||
|
isExynos4 = MediaCodecHelper.isExynos4Device();
|
||||||
|
if (needsSpsBitstreamFixup) {
|
||||||
|
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs SPS bitstream restrictions fixup");
|
||||||
|
}
|
||||||
|
if (needsBaselineSpsHack) {
|
||||||
|
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs baseline SPS hack");
|
||||||
|
}
|
||||||
|
if (constrainedHighProfile) {
|
||||||
|
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs constrained high profile");
|
||||||
|
}
|
||||||
|
if (isExynos4) {
|
||||||
|
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" is on Exynos 4");
|
||||||
|
}
|
||||||
|
|
||||||
|
refFrameInvalidationActive = refFrameInvalidationAvc;
|
||||||
|
}
|
||||||
|
else if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H265) != 0) {
|
||||||
|
mimeType = "video/hevc";
|
||||||
|
selectedDecoderInfo = hevcDecoder;
|
||||||
|
|
||||||
|
if (hevcDecoder == null) {
|
||||||
|
LimeLog.severe("No available HEVC decoder!");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
refFrameInvalidationActive = refFrameInvalidationHevc;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unknown format
|
||||||
|
LimeLog.severe("Unknown format");
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderInfo, mimeType);
|
||||||
|
fusedIdrFrame = MediaCodecHelper.decoderSupportsFusedIdrFrame(selectedDecoderInfo, mimeType);
|
||||||
|
|
||||||
|
for (int tryNumber = 0;; tryNumber++) {
|
||||||
|
LimeLog.info("Decoder configuration try: "+tryNumber);
|
||||||
|
|
||||||
|
MediaFormat mediaFormat = createBaseMediaFormat(mimeType);
|
||||||
|
|
||||||
|
// This will try low latency options until we find one that works (or we give up).
|
||||||
|
boolean newFormat = MediaCodecHelper.setDecoderLowLatencyOptions(mediaFormat, selectedDecoderInfo, tryNumber);
|
||||||
|
|
||||||
|
if (tryConfigureDecoder(selectedDecoderInfo, mediaFormat)) {
|
||||||
|
// Success!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newFormat) {
|
||||||
|
// We couldn't even configure a decoder without any low latency options
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -440,6 +506,66 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFrame(long frameTimeNanos) {
|
||||||
|
// Do nothing if we're stopping
|
||||||
|
if (stopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't render unless a new frame is due. This prevents microstutter when streaming
|
||||||
|
// at a frame rate that doesn't match the display (such as 60 FPS on 120 Hz).
|
||||||
|
long actualFrameTimeDeltaNs = frameTimeNanos - lastRenderedFrameTimeNanos;
|
||||||
|
long expectedFrameTimeDeltaNs = 800000000 / refreshRate; // within 80% of the next frame
|
||||||
|
if (actualFrameTimeDeltaNs >= expectedFrameTimeDeltaNs) {
|
||||||
|
// Render up to one frame when in frame pacing mode.
|
||||||
|
//
|
||||||
|
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
|
||||||
|
// by holding onto them for too long. This also ensures we will have that 1 extra
|
||||||
|
// frame of buffer to smooth over network/rendering jitter.
|
||||||
|
Integer nextOutputBuffer = outputBufferQueue.poll();
|
||||||
|
if (nextOutputBuffer != null) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRenderedFrameTimeNanos = frameTimeNanos;
|
||||||
|
activeWindowVideoStats.totalFramesRendered++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// This will leak nextOutputBuffer, but there's really nothing else we can do
|
||||||
|
handleDecoderException(e, null, 0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request another callback for next frame
|
||||||
|
Choreographer.getInstance().postFrameCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startChoreographerThread() {
|
||||||
|
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||||
|
// Not using Choreographer in this pacing mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a separate thread to avoid any main thread delays from delaying rendering
|
||||||
|
choreographerHandlerThread = new HandlerThread("Video - Choreographer", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_MORE_FAVORABLE);
|
||||||
|
choreographerHandlerThread.start();
|
||||||
|
|
||||||
|
// Start the frame callbacks
|
||||||
|
choreographerHandler = new Handler(choreographerHandlerThread.getLooper());
|
||||||
|
choreographerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void startRendererThread()
|
private void startRendererThread()
|
||||||
{
|
{
|
||||||
rendererThread = new Thread() {
|
rendererThread = new Thread() {
|
||||||
@@ -454,32 +580,60 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
long presentationTimeUs = info.presentationTimeUs;
|
long presentationTimeUs = info.presentationTimeUs;
|
||||||
int lastIndex = outIndex;
|
int lastIndex = outIndex;
|
||||||
|
|
||||||
// Get the last output buffer in the queue
|
numFramesOut++;
|
||||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
|
||||||
|
|
||||||
lastIndex = outIndex;
|
// Render the latest frame now if frame pacing isn't in balanced mode
|
||||||
presentationTimeUs = info.presentationTimeUs;
|
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||||
}
|
// Get the last output buffer in the queue
|
||||||
|
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||||
|
|
||||||
// Render the last buffer
|
numFramesOut++;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
if (legacyFrameDropRendering) {
|
lastIndex = outIndex;
|
||||||
// Use a PTS that will cause this frame to be dropped if another comes in within
|
presentationTimeUs = info.presentationTimeUs;
|
||||||
// the same V-sync period
|
}
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime());
|
|
||||||
|
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS ||
|
||||||
|
prefs.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
|
||||||
|
// In max smoothness or cap FPS mode, we want to never drop frames
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// Use a PTS that will cause this frame to never be dropped
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Use a PTS that will cause this frame to never be dropped if frame dropping
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
// is disabled
|
// Use a PTS that will cause this frame to be dropped if another comes in within
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, 0);
|
// the same V-sync period
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeWindowVideoStats.totalFramesRendered++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
// For balanced frame pacing case, the Choreographer callback will handle rendering.
|
||||||
}
|
// We just put all frames into the output buffer queue and let it handle things.
|
||||||
|
|
||||||
activeWindowVideoStats.totalFramesRendered++;
|
// Discard the oldest buffer if we've exceeded our limit.
|
||||||
|
//
|
||||||
|
// NB: We have to do this on the producer side because the consumer may not
|
||||||
|
// run for a while (if there is a huge mismatch between stream FPS and display
|
||||||
|
// refresh rate).
|
||||||
|
if (outputBufferQueue.size() == OUTPUT_BUFFER_QUEUE_LIMIT) {
|
||||||
|
videoDecoder.releaseOutputBuffer(outputBufferQueue.take(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this buffer
|
||||||
|
outputBufferQueue.add(lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// Add delta time to the totals (excluding probable outliers)
|
// Add delta time to the totals (excluding probable outliers)
|
||||||
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
||||||
@@ -554,6 +708,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
startRendererThread();
|
startRendererThread();
|
||||||
|
startChoreographerThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// !!! May be called even if setup()/start() fails !!!
|
// !!! May be called even if setup()/start() fails !!!
|
||||||
@@ -565,6 +720,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
if (rendererThread != null) {
|
if (rendererThread != null) {
|
||||||
rendererThread.interrupt();
|
rendererThread.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Post a quit message to the Choreographer looper (if we have one)
|
||||||
|
if (choreographerHandler != null) {
|
||||||
|
choreographerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Don't allow any further messages to be queued
|
||||||
|
choreographerHandlerThread.quit();
|
||||||
|
|
||||||
|
// Deregister the frame callback (if registered)
|
||||||
|
Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -572,10 +741,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
// May be called already, but we'll call it now to be safe
|
// May be called already, but we'll call it now to be safe
|
||||||
prepareForStop();
|
prepareForStop();
|
||||||
|
|
||||||
|
// Wait for the Choreographer looper to shut down (if we have one)
|
||||||
|
if (choreographerHandlerThread != null) {
|
||||||
|
try {
|
||||||
|
choreographerHandlerThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the renderer thread to shut down
|
// Wait for the renderer thread to shut down
|
||||||
try {
|
try {
|
||||||
rendererThread.join();
|
rendererThread.join();
|
||||||
} catch (InterruptedException ignored) { }
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -583,6 +773,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
videoDecoder.release();
|
videoDecoder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHdrMode(boolean enabled) {
|
||||||
|
// TODO: Set HDR metadata?
|
||||||
|
}
|
||||||
|
|
||||||
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||||
try {
|
try {
|
||||||
videoDecoder.queueInputBuffer(inputBufferIndex,
|
videoDecoder.queueInputBuffer(inputBufferIndex,
|
||||||
@@ -638,7 +833,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs) {
|
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs) {
|
||||||
if (stopping) {
|
if (stopping) {
|
||||||
// Don't bother if we're stopping
|
// Don't bother if we're stopping
|
||||||
return MoonBridge.DR_OK;
|
return MoonBridge.DR_OK;
|
||||||
@@ -674,17 +869,18 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float decodeTimeMs = (float)lastTwo.decoderTimeMs / lastTwo.totalFramesReceived;
|
float decodeTimeMs = (float)lastTwo.decoderTimeMs / lastTwo.totalFramesReceived;
|
||||||
String perfText = context.getString(
|
long rttInfo = MoonBridge.getEstimatedRttInfo();
|
||||||
R.string.perf_overlay_text,
|
StringBuilder sb = new StringBuilder();
|
||||||
initialWidth + "x" + initialHeight,
|
sb.append(context.getString(R.string.perf_overlay_streamdetails, initialWidth + "x" + initialHeight, fps.totalFps)).append('\n');
|
||||||
decoder,
|
sb.append(context.getString(R.string.perf_overlay_decoder, decoder)).append('\n');
|
||||||
fps.totalFps,
|
sb.append(context.getString(R.string.perf_overlay_incomingfps, fps.receivedFps)).append('\n');
|
||||||
fps.receivedFps,
|
sb.append(context.getString(R.string.perf_overlay_renderingfps, fps.renderedFps)).append('\n');
|
||||||
fps.renderedFps,
|
sb.append(context.getString(R.string.perf_overlay_netdrops,
|
||||||
(float)lastTwo.framesLost / lastTwo.totalFrames * 100,
|
(float)lastTwo.framesLost / lastTwo.totalFrames * 100)).append('\n');
|
||||||
((float)lastTwo.totalTimeMs / lastTwo.totalFramesReceived) - decodeTimeMs,
|
sb.append(context.getString(R.string.perf_overlay_netlatency,
|
||||||
decodeTimeMs);
|
(int)(rttInfo >> 32), (int)rttInfo)).append('\n');
|
||||||
perfListener.onPerfUpdate(perfText);
|
sb.append(context.getString(R.string.perf_overlay_dectime, decodeTimeMs));
|
||||||
|
perfListener.onPerfUpdate(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
globalVideoStats.add(activeWindowVideoStats);
|
globalVideoStats.add(activeWindowVideoStats);
|
||||||
@@ -693,29 +889,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
|
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
activeWindowVideoStats.totalFramesReceived++;
|
|
||||||
activeWindowVideoStats.totalFrames++;
|
|
||||||
|
|
||||||
int inputBufferIndex;
|
int inputBufferIndex;
|
||||||
ByteBuffer buf;
|
ByteBuffer buf;
|
||||||
|
long timestampUs;
|
||||||
long timestampUs = enqueueTimeMs * 1000;
|
|
||||||
|
|
||||||
if (!FRAME_RENDER_TIME_ONLY) {
|
|
||||||
// Count time from first packet received to enqueue time as receive time
|
|
||||||
// We will count DU queue time as part of decoding, because it is directly
|
|
||||||
// caused by a slow decoder.
|
|
||||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestampUs <= lastTimestampUs) {
|
|
||||||
// We can't submit multiple buffers with the same timestamp
|
|
||||||
// so bump it up by one before queuing
|
|
||||||
timestampUs = lastTimestampUs + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTimestampUs = timestampUs;
|
|
||||||
|
|
||||||
int codecFlags = 0;
|
int codecFlags = 0;
|
||||||
|
|
||||||
// H264 SPS
|
// H264 SPS
|
||||||
@@ -855,9 +1031,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
numPpsIn++;
|
numPpsIn++;
|
||||||
|
|
||||||
// If this is the first CSD blob or we aren't supporting
|
// If this is the first CSD blob or we aren't supporting
|
||||||
// adaptive playback, we will submit the CSD blob in a
|
// fused IDR frames, we will submit the CSD blob in a
|
||||||
// separate input buffer.
|
// separate input buffer.
|
||||||
if (!submittedCsd || !adaptivePlayback) {
|
if (!submittedCsd || !fusedIdrFrame) {
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
inputBufferIndex = dequeueInputBuffer();
|
||||||
if (inputBufferIndex < 0) {
|
if (inputBufferIndex < 0) {
|
||||||
// We're being torn down now
|
// We're being torn down now
|
||||||
@@ -881,6 +1057,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
|
|
||||||
// This is the CSD blob
|
// This is the CSD blob
|
||||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||||
|
timestampUs = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Batch this to submit together with the next I-frame
|
// Batch this to submit together with the next I-frame
|
||||||
@@ -894,6 +1071,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
activeWindowVideoStats.totalFramesReceived++;
|
||||||
|
activeWindowVideoStats.totalFrames++;
|
||||||
|
|
||||||
|
if (!FRAME_RENDER_TIME_ONLY) {
|
||||||
|
// Count time from first packet received to enqueue time as receive time
|
||||||
|
// We will count DU queue time as part of decoding, because it is directly
|
||||||
|
// caused by a slow decoder.
|
||||||
|
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
inputBufferIndex = dequeueInputBuffer();
|
||||||
if (inputBufferIndex < 0) {
|
if (inputBufferIndex < 0) {
|
||||||
// We're being torn down now
|
// We're being torn down now
|
||||||
@@ -919,6 +1106,22 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
|
|
||||||
submitCsdNextCall = false;
|
submitCsdNextCall = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frameType == MoonBridge.FRAME_TYPE_IDR) {
|
||||||
|
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampUs = enqueueTimeMs * 1000;
|
||||||
|
|
||||||
|
if (timestampUs <= lastTimestampUs) {
|
||||||
|
// We can't submit multiple buffers with the same timestamp
|
||||||
|
// so bump it up by one before queuing
|
||||||
|
timestampUs = lastTimestampUs + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTimestampUs = timestampUs;
|
||||||
|
|
||||||
|
numFramesIn++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decodeUnitLength > buf.limit() - buf.position()) {
|
if (decodeUnitLength > buf.limit() - buf.position()) {
|
||||||
@@ -988,8 +1191,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
// Queue the new SPS
|
// Queue the new SPS
|
||||||
return queueInputBuffer(inputIndex,
|
return queueInputBuffer(inputIndex,
|
||||||
0, inputBuffer.position(),
|
0, inputBuffer.position(),
|
||||||
System.nanoTime() / 1000,
|
0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||||
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1064,8 +1266,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String generateText(MediaCodecDecoderRenderer renderer, Exception originalException, ByteBuffer currentBuffer, int currentCodecFlags) {
|
private String generateText(MediaCodecDecoderRenderer renderer, Exception originalException, ByteBuffer currentBuffer, int currentCodecFlags) {
|
||||||
String str = "";
|
String str;
|
||||||
|
|
||||||
|
if (renderer.numVpsIn == 0 && renderer.numSpsIn == 0 && renderer.numPpsIn == 0) {
|
||||||
|
str = "PreSPSError";
|
||||||
|
}
|
||||||
|
else if (renderer.numSpsIn > 0 && renderer.numPpsIn == 0) {
|
||||||
|
str = "PrePPSError";
|
||||||
|
}
|
||||||
|
else if (renderer.numPpsIn > 0 && renderer.numFramesIn == 0) {
|
||||||
|
str = "PreIFrameError";
|
||||||
|
}
|
||||||
|
else if (renderer.numFramesIn > 0 && renderer.outputFormat == null) {
|
||||||
|
str = "PreOutputConfigError";
|
||||||
|
}
|
||||||
|
else if (renderer.outputFormat != null && renderer.numFramesOut == 0) {
|
||||||
|
str = "PreOutputError";
|
||||||
|
}
|
||||||
|
else if (renderer.numFramesOut <= renderer.refreshRate * 30) {
|
||||||
|
str = "EarlyOutputError";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str = "ErrorWhileStreaming";
|
||||||
|
}
|
||||||
|
|
||||||
|
str += ": 1\n";
|
||||||
str += "Format: "+String.format("%x", renderer.videoFormat)+"\n";
|
str += "Format: "+String.format("%x", renderer.videoFormat)+"\n";
|
||||||
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
||||||
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
||||||
@@ -1099,20 +1324,37 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
str += "Adaptive playback: "+renderer.adaptivePlayback+"\n";
|
str += "Adaptive playback: "+renderer.adaptivePlayback+"\n";
|
||||||
str += "GL Renderer: "+renderer.glRenderer+"\n";
|
str += "GL Renderer: "+renderer.glRenderer+"\n";
|
||||||
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
|
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\n";
|
||||||
|
str += "Performance class: "+Build.VERSION.MEDIA_PERFORMANCE_CLASS+"\n";
|
||||||
|
str += "Vendor params: ";
|
||||||
|
List<String> params = renderer.videoDecoder.getSupportedVendorParameters();
|
||||||
|
if (params.isEmpty()) {
|
||||||
|
str += "NONE";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (String param : params) {
|
||||||
|
str += param + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str += "\n";
|
||||||
|
}
|
||||||
str += "Foreground: "+renderer.foreground+"\n";
|
str += "Foreground: "+renderer.foreground+"\n";
|
||||||
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
||||||
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
|
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
|
||||||
str += "Using modern SPS patching: "+(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)+"\n";
|
str += "Using modern SPS patching: "+(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)+"\n";
|
||||||
str += "Low latency mode: "+renderer.lowLatency+"\n";
|
str += "Fused IDR frames: "+renderer.fusedIdrFrame+"\n";
|
||||||
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||||
str += "FPS target: "+renderer.refreshRate+"\n";
|
str += "FPS target: "+renderer.refreshRate+"\n";
|
||||||
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
|
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
|
||||||
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
str += "CSD stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
||||||
|
str += "Frames in-out: "+renderer.numFramesIn+", "+renderer.numFramesOut+"\n";
|
||||||
str += "Total frames received: "+renderer.globalVideoStats.totalFramesReceived+"\n";
|
str += "Total frames received: "+renderer.globalVideoStats.totalFramesReceived+"\n";
|
||||||
str += "Total frames rendered: "+renderer.globalVideoStats.totalFramesRendered+"\n";
|
str += "Total frames rendered: "+renderer.globalVideoStats.totalFramesRendered+"\n";
|
||||||
str += "Frame losses: "+renderer.globalVideoStats.framesLost+" in "+renderer.globalVideoStats.frameLossEvents+" loss events\n";
|
str += "Frame losses: "+renderer.globalVideoStats.framesLost+" in "+renderer.globalVideoStats.frameLossEvents+" loss events\n";
|
||||||
str += "Average end-to-end client latency: "+renderer.getAverageEndToEndLatency()+"ms\n";
|
str += "Average end-to-end client latency: "+renderer.getAverageEndToEndLatency()+"ms\n";
|
||||||
str += "Average hardware decoder latency: "+renderer.getAverageDecoderLatency()+"ms\n";
|
str += "Average hardware decoder latency: "+renderer.getAverageDecoderLatency()+"ms\n";
|
||||||
|
str += "Frame pacing mode: "+renderer.prefs.framePacing+"\n";
|
||||||
|
|
||||||
if (currentBuffer != null) {
|
if (currentBuffer != null) {
|
||||||
str += "Current buffer: ";
|
str += "Current buffer: ";
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import android.media.MediaCodecInfo;
|
|||||||
import android.media.MediaCodecList;
|
import android.media.MediaCodecList;
|
||||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
public class MediaCodecHelper {
|
public class MediaCodecHelper {
|
||||||
|
|
||||||
@@ -36,9 +38,10 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> whitelistedHevcDecoders;
|
private static final List<String> whitelistedHevcDecoders;
|
||||||
private static final List<String> refFrameInvalidationAvcPrefixes;
|
private static final List<String> refFrameInvalidationAvcPrefixes;
|
||||||
private static final List<String> refFrameInvalidationHevcPrefixes;
|
private static final List<String> refFrameInvalidationHevcPrefixes;
|
||||||
private static final List<String> blacklisted49FpsDecoderPrefixes;
|
|
||||||
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
|
||||||
private static final List<String> qualcommDecoderPrefixes;
|
private static final List<String> qualcommDecoderPrefixes;
|
||||||
|
private static final List<String> kirinDecoderPrefixes;
|
||||||
|
private static final List<String> exynosDecoderPrefixes;
|
||||||
|
private static final List<String> amlogicDecoderPrefixes;
|
||||||
|
|
||||||
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ public class MediaCodecHelper {
|
|||||||
// if adaptive playback was enabled so let's avoid it to be safe.
|
// if adaptive playback was enabled so let's avoid it to be safe.
|
||||||
blacklistedAdaptivePlaybackPrefixes.add("omx.intel");
|
blacklistedAdaptivePlaybackPrefixes.add("omx.intel");
|
||||||
// The MediaTek decoder crashes at 1080p when adaptive playback is enabled
|
// The MediaTek decoder crashes at 1080p when adaptive playback is enabled
|
||||||
// on some Android TV devices with H.265 only.
|
// on some Android TV devices with HEVC only.
|
||||||
blacklistedAdaptivePlaybackPrefixes.add("omx.mtk");
|
blacklistedAdaptivePlaybackPrefixes.add("omx.mtk");
|
||||||
|
|
||||||
constrainedHighProfilePrefixes = new LinkedList<>();
|
constrainedHighProfilePrefixes = new LinkedList<>();
|
||||||
@@ -133,21 +136,22 @@ public class MediaCodecHelper {
|
|||||||
// Exynos seems to be the only HEVC decoder that works reliably
|
// Exynos seems to be the only HEVC decoder that works reliably
|
||||||
whitelistedHevcDecoders.add("omx.exynos");
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
|
|
||||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
|
||||||
// For some reason, other X1 implementations require bitstream fixups.
|
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
|
||||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
// has been supported in GFE since late 2017, we'll go ahead and enable HEVC for all
|
||||||
|
// device models.
|
||||||
|
//
|
||||||
|
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||||
|
// whether the performance is good enough to use for streaming, but they're
|
||||||
|
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||||
|
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
||||||
|
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
||||||
|
// check for it here. Since there are 2 models of Shield Tablet (possibly
|
||||||
|
// more with LTE), I will also exclude pre-Oreo OSes since only Shield ATV
|
||||||
|
// got an Oreo update.
|
||||||
|
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
whitelistedHevcDecoders.add("omx.nvidia");
|
whitelistedHevcDecoders.add("omx.nvidia");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
|
||||||
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
|
||||||
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
|
||||||
whitelistedHevcDecoders.add("omx.mtk");
|
|
||||||
whitelistedHevcDecoders.add("omx.amlogic");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
|
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
|
||||||
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
|
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
|
||||||
@@ -157,11 +161,23 @@ public class MediaCodecHelper {
|
|||||||
|
|
||||||
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
||||||
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
|
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
|
||||||
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
|
// running Android 9 or later.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
//
|
||||||
|
// NB: We don't do this on Sabrina (GCWGTV) because H.264 is lower latency when we use
|
||||||
|
// vendor.low-latency.enable. We will still use HEVC if decoderCanMeetPerformancePointWithHevcAndNotAvc()
|
||||||
|
// determines it's the only way to meet the performance requirements.
|
||||||
|
//
|
||||||
|
// FIXME: Should we do this for all Amlogic S905X SoCs?
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Build.DEVICE.equalsIgnoreCase("sabrina")) {
|
||||||
whitelistedHevcDecoders.add("omx.amlogic");
|
whitelistedHevcDecoders.add("omx.amlogic");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Realtek SoCs are used inside many Android TV devices and can only do 4K60 with HEVC.
|
||||||
|
// We'll enable those HEVC decoders by default and see if anything breaks.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
whitelistedHevcDecoders.add("omx.realtek");
|
||||||
|
}
|
||||||
|
|
||||||
// These theoretically have good HEVC decoding capabilities (potentially better than
|
// These theoretically have good HEVC decoding capabilities (potentially better than
|
||||||
// their AVC decoders), but haven't been tested enough
|
// their AVC decoders), but haven't been tested enough
|
||||||
//whitelistedHevcDecoders.add("omx.rk");
|
//whitelistedHevcDecoders.add("omx.rk");
|
||||||
@@ -181,24 +197,6 @@ public class MediaCodecHelper {
|
|||||||
// Qualcomm is currently the only decoders in this group.
|
// Qualcomm is currently the only decoders in this group.
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
|
||||||
blacklisted49FpsDecoderPrefixes = new LinkedList<>();
|
|
||||||
blacklisted59FpsDecoderPrefixes = new LinkedList<>();
|
|
||||||
|
|
||||||
// We see a bunch of crashes on MediaTek Android TVs running
|
|
||||||
// at 49 FPS (PAL 50 Hz - 1). Blacklist this frame rate for
|
|
||||||
// these devices and hope they fix it in Pie.
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
|
||||||
blacklisted49FpsDecoderPrefixes.add("omx.mtk");
|
|
||||||
|
|
||||||
// 59 FPS also seems to crash on the Sony Bravia TV ATV3 model.
|
|
||||||
// Blacklist that frame rate on these devices too.
|
|
||||||
if (Build.DEVICE.startsWith("BRAVIA_ATV3")) {
|
|
||||||
blacklisted59FpsDecoderPrefixes.add("omx.mtk");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
qualcommDecoderPrefixes = new LinkedList<>();
|
qualcommDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
@@ -206,6 +204,24 @@ public class MediaCodecHelper {
|
|||||||
qualcommDecoderPrefixes.add("c2.qti");
|
qualcommDecoderPrefixes.add("c2.qti");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
kirinDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
|
kirinDecoderPrefixes.add("omx.hisi");
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
exynosDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
|
exynosDecoderPrefixes.add("omx.exynos");
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
amlogicDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
|
amlogicDecoderPrefixes.add("omx.amlogic");
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isPowerVR(String glRenderer) {
|
private static boolean isPowerVR(String glRenderer) {
|
||||||
return glRenderer.toLowerCase().contains("powervr");
|
return glRenderer.toLowerCase().contains("powervr");
|
||||||
}
|
}
|
||||||
@@ -264,6 +280,19 @@ public class MediaCodecHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||||
|
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
||||||
|
// We still have to check Build.MANUFACTURER to catch Amazon Fire tablets.
|
||||||
|
if (context.getPackageManager().hasSystemFeature("amazon.hardware.fire_tv") ||
|
||||||
|
Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||||
|
whitelistedHevcDecoders.add("omx.mtk");
|
||||||
|
|
||||||
|
// This requires setting vdec-lowlatency on the Fire TV 3, otherwise the decoder
|
||||||
|
// never produces any output frames. See comment above for details on why we only
|
||||||
|
// do this for Fire TV devices.
|
||||||
|
whitelistedHevcDecoders.add("omx.amlogic");
|
||||||
|
}
|
||||||
|
|
||||||
ActivityManager activityManager =
|
ActivityManager activityManager =
|
||||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||||
@@ -352,7 +381,7 @@ public class MediaCodecHelper {
|
|||||||
return System.nanoTime() / 1000000L;
|
return System.nanoTime() / 1000000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
private static boolean decoderSupportsAndroidRLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
try {
|
try {
|
||||||
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
|
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
|
||||||
@@ -368,7 +397,7 @@ public class MediaCodecHelper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsMaxOperatingRate(String decoderName) {
|
private static boolean decoderSupportsMaxOperatingRate(String decoderName) {
|
||||||
// Operate at maximum rate to lower latency as much as possible on
|
// Operate at maximum rate to lower latency as much as possible on
|
||||||
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
|
// 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
|
// but that will actually result in the decoder crashing if it can't satisfy
|
||||||
@@ -383,6 +412,115 @@ public class MediaCodecHelper {
|
|||||||
!isAdreno620;
|
!isAdreno620;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, int tryNumber) {
|
||||||
|
// Options here should be tried in the order of most to least risky. The decoder will use
|
||||||
|
// the first MediaFormat that doesn't fail in configure().
|
||||||
|
|
||||||
|
boolean setNewOption = false;
|
||||||
|
|
||||||
|
if (tryNumber < 1) {
|
||||||
|
// Official Android 11+ low latency option (KEY_LOW_LATENCY).
|
||||||
|
videoFormat.setInteger("low-latency", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
|
||||||
|
// If this decoder officially supports FEATURE_LowLatency, we will just use that alone
|
||||||
|
// for try 0. Otherwise, we'll include it as best effort with other options.
|
||||||
|
if (decoderSupportsAndroidRLowLatency(decoderInfo, videoFormat.getString(MediaFormat.KEY_MIME))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryNumber < 2) {
|
||||||
|
// MediaTek decoders don't use vendor-defined keys for low latency mode. Instead, they have a modified
|
||||||
|
// version of AOSP's ACodec.cpp which supports the "vdec-lowlatency" option. This option is passed down
|
||||||
|
// to the decoder as OMX.MTK.index.param.video.LowLatencyDecode.
|
||||||
|
//
|
||||||
|
// This option is also plumbed for Amazon Amlogic-based devices like the Fire TV 3. Not only does it
|
||||||
|
// reduce latency on Amlogic, it fixes the HEVC bug that causes the decoder to not output any frames.
|
||||||
|
// On Fire TV 3, vdec-lowlatency is translated to OMX.amazon.fireos.index.video.lowLatencyDecode.
|
||||||
|
//
|
||||||
|
// https://github.com/yuan1617/Framwork/blob/master/frameworks/av/media/libstagefright/ACodec.cpp
|
||||||
|
// https://github.com/iykex/vendor_mediatek_proprietary_hardware/blob/master/libomx/video/MtkOmxVdecEx/MtkOmxVdecEx.h
|
||||||
|
videoFormat.setInteger("vdec-lowlatency", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||||
|
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||||
|
//
|
||||||
|
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||||
|
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// Try vendor-specific low latency options
|
||||||
|
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||||
|
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||||
|
//
|
||||||
|
// We will first try both, then try vendor.qti-ext-dec-low-latency.enable alone if that fails
|
||||||
|
if (tryNumber < 3) {
|
||||||
|
videoFormat.setInteger("vendor.qti-ext-dec-picture-order.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
if (tryNumber < 4) {
|
||||||
|
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
if (tryNumber < 3) {
|
||||||
|
// Kirin low latency options
|
||||||
|
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
|
||||||
|
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
|
||||||
|
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
if (tryNumber < 3) {
|
||||||
|
// Exynos low latency option for H.264 decoder
|
||||||
|
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isDecoderInList(amlogicDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
if (tryNumber < 3) {
|
||||||
|
// Amlogic low latency vendor extension
|
||||||
|
// https://github.com/codewalkerster/android_vendor_amlogic_common_prebuilt_libstagefrighthw/commit/41fefc4e035c476d58491324a5fe7666bfc2989e
|
||||||
|
videoFormat.setInteger("vendor.low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: We should probably integrate this into the try system
|
||||||
|
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
||||||
|
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setNewOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
||||||
|
// If adaptive playback is supported, we can submit new CSD together with a keyframe
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
try {
|
||||||
|
if (decoderInfo.getCapabilitiesForType(mimeType).
|
||||||
|
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||||
|
{
|
||||||
|
LimeLog.info("Decoder supports fused IDR frames (FEATURE_AdaptivePlayback)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Tolerate buggy codecs
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
||||||
// Possibly enable adaptive playback on KitKat and above
|
// Possibly enable adaptive playback on KitKat and above
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
@@ -408,13 +546,6 @@ public class MediaCodecHelper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsQcomVendorLowLatency(String decoderName) {
|
|
||||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
|
||||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
||||||
isDecoderInList(qualcommDecoderPrefixes, decoderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean decoderNeedsConstrainedHighProfile(String decoderName) {
|
public static boolean decoderNeedsConstrainedHighProfile(String decoderName) {
|
||||||
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
||||||
}
|
}
|
||||||
@@ -431,18 +562,6 @@ public class MediaCodecHelper {
|
|||||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderBlacklistedForFrameRate(String decoderName, int fps) {
|
|
||||||
if (fps == 49) {
|
|
||||||
return isDecoderInList(blacklisted49FpsDecoderPrefixes, decoderName);
|
|
||||||
}
|
|
||||||
else if (fps == 59) {
|
|
||||||
return isDecoderInList(blacklisted59FpsDecoderPrefixes, decoderName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean decoderSupportsRefFrameInvalidationAvc(String decoderName, int videoHeight) {
|
public static boolean decoderSupportsRefFrameInvalidationAvc(String decoderName, int videoHeight) {
|
||||||
// Reference frame invalidation is broken on low-end Snapdragon SoCs at 1080p.
|
// Reference frame invalidation is broken on low-end Snapdragon SoCs at 1080p.
|
||||||
if (videoHeight > 720 && isLowEndSnapdragon) {
|
if (videoHeight > 720 && isLowEndSnapdragon) {
|
||||||
@@ -462,21 +581,7 @@ public class MediaCodecHelper {
|
|||||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData) {
|
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
|
||||||
// TODO: Shield Tablet K1/LTE?
|
|
||||||
//
|
|
||||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
|
||||||
// whether the performance is good enough to use for streaming, but they're
|
|
||||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
|
||||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
|
||||||
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
|
||||||
// check for it here.
|
|
||||||
//
|
|
||||||
// TODO: Temporarily disabled with NVIDIA HEVC support
|
|
||||||
/*if (Build.DEVICE.equalsIgnoreCase("shieldtablet")) {
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
||||||
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
||||||
// so I'm restricting HEVC usage to Lollipop and higher.
|
// so I'm restricting HEVC usage to Lollipop and higher.
|
||||||
@@ -497,9 +602,10 @@ public class MediaCodecHelper {
|
|||||||
// Some devices have HEVC decoders that we prefer not to use
|
// Some devices have HEVC decoders that we prefer not to use
|
||||||
// typically because it can't support reference frame invalidation.
|
// typically because it can't support reference frame invalidation.
|
||||||
// However, we will use it for HDR and for streaming over mobile networks
|
// However, we will use it for HDR and for streaming over mobile networks
|
||||||
// since it works fine otherwise.
|
// since it works fine otherwise. We will also use it for 4K because RFI
|
||||||
|
// is currently disabled due to issues with video corruption.
|
||||||
if (isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
if (isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||||
if (meteredData) {
|
if (meteredData || (prefs.width == 3840 && prefs.height == 2160)) {
|
||||||
LimeLog.info("Selected deprioritized decoder");
|
LimeLog.info("Selected deprioritized decoder");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -566,13 +672,6 @@ public class MediaCodecHelper {
|
|||||||
if (codecInfo.isEncoder()) {
|
if (codecInfo.isEncoder()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip compatibility aliases on Q+
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
if (codecInfo.isAlias()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for preferred decoders
|
// Check for preferred decoders
|
||||||
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
|
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
|
||||||
@@ -658,43 +757,57 @@ public class MediaCodecHelper {
|
|||||||
// and we want to be sure all callers are handling this possibility
|
// and we want to be sure all callers are handling this possibility
|
||||||
@SuppressWarnings("RedundantThrows")
|
@SuppressWarnings("RedundantThrows")
|
||||||
private static MediaCodecInfo findKnownSafeDecoder(String mimeType, int requiredProfile) throws Exception {
|
private static MediaCodecInfo findKnownSafeDecoder(String mimeType, int requiredProfile) throws Exception {
|
||||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
// Some devices (Exynos devces, at least) have two sets of decoders.
|
||||||
// Skip encoders
|
// The first set of decoders are C2 which do not support FEATURE_LowLatency,
|
||||||
if (codecInfo.isEncoder()) {
|
// but the second set of OMX decoders do support FEATURE_LowLatency. We want
|
||||||
continue;
|
// to pick the OMX decoders despite the fact that C2 is listed first.
|
||||||
}
|
// On some Qualcomm devices (like Pixel 4), there are separate low latency decoders
|
||||||
|
// (like c2.qti.hevc.decoder.low_latency) that advertise FEATURE_LowLatency while
|
||||||
// Skip compatibility aliases on Q+
|
// the standard ones (like c2.qti.hevc.decoder) do not. Like Exynos, the decoders
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
// with FEATURE_LowLatency support are listed after the standard ones.
|
||||||
if (codecInfo.isAlias()) {
|
for (int i = 0; i < 2; i++) {
|
||||||
|
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||||
|
// Skip encoders
|
||||||
|
if (codecInfo.isEncoder()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Find a decoder that supports the requested video format
|
|
||||||
for (String mime : codecInfo.getSupportedTypes()) {
|
|
||||||
if (mime.equalsIgnoreCase(mimeType)) {
|
|
||||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
|
||||||
|
|
||||||
// Skip blacklisted codecs
|
// Skip compatibility aliases on Q+
|
||||||
if (isCodecBlacklisted(codecInfo)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
if (codecInfo.isAlias()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
// Find a decoder that supports the requested video format
|
||||||
|
for (String mime : codecInfo.getSupportedTypes()) {
|
||||||
|
if (mime.equalsIgnoreCase(mimeType)) {
|
||||||
|
LimeLog.info("Examining decoder capabilities of " + codecInfo.getName() + " (round " + (i + 1) + ")");
|
||||||
|
|
||||||
if (requiredProfile != -1) {
|
// Skip blacklisted codecs
|
||||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
if (isCodecBlacklisted(codecInfo)) {
|
||||||
if (profile.profile == requiredProfile) {
|
continue;
|
||||||
LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile");
|
|
||||||
return codecInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile");
|
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||||
}
|
|
||||||
else {
|
if (i == 0 && !decoderSupportsAndroidRLowLatency(codecInfo, mime)) {
|
||||||
return codecInfo;
|
LimeLog.info("Skipping decoder that lacks FEATURE_LowLatency for round 1");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredProfile != -1) {
|
||||||
|
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||||
|
if (profile.profile == requiredProfile) {
|
||||||
|
LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile");
|
||||||
|
return codecInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile");
|
||||||
|
} else {
|
||||||
|
return codecInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import java.io.OutputStream;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -49,8 +47,7 @@ public class ComputerManagerService extends Service {
|
|||||||
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
|
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
|
||||||
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
|
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
|
||||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||||
private static final int FAST_POLL_TIMEOUT = 1000;
|
private static final int OFFLINE_POLL_TRIES = 3;
|
||||||
private static final int OFFLINE_POLL_TRIES = 5;
|
|
||||||
private static final int INITIAL_POLL_TRIES = 2;
|
private static final int INITIAL_POLL_TRIES = 2;
|
||||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||||
private static final int POLL_DATA_TTL_MS = 30000;
|
private static final int POLL_DATA_TTL_MS = 30000;
|
||||||
@@ -232,7 +229,13 @@ public class ComputerManagerService extends Service {
|
|||||||
// Wait for the bind notification
|
// Wait for the bind notification
|
||||||
discoveryServiceConnection.wait(1000);
|
discoveryServiceConnection.wait(1000);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,11 +244,18 @@ public class ComputerManagerService extends Service {
|
|||||||
while (activePolls.get() != 0) {
|
while (activePolls.get() != 0) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(250);
|
Thread.sleep(250);
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||||
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
|
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,9 +409,18 @@ public class ComputerManagerService extends Service {
|
|||||||
details.ipv6Address = computer.getIpv6Address().getHostAddress();
|
details.ipv6Address = computer.getIpv6Address().getHostAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kick off a serverinfo poll on this machine
|
try {
|
||||||
if (!addComputerBlocking(details)) {
|
// Kick off a blocking serverinfo poll on this machine
|
||||||
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
if (!addComputerBlocking(details)) {
|
||||||
|
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,28 +468,25 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||||
// Block while we try to fill the details
|
// Block while we try to fill the details
|
||||||
try {
|
|
||||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||||
if (pollComputer(fakeDetails)) {
|
if (pollComputer(fakeDetails)) {
|
||||||
// See if we have record of this PC to pull its pinned cert
|
// See if we have record of this PC to pull its pinned cert
|
||||||
synchronized (pollingTuples) {
|
synchronized (pollingTuples) {
|
||||||
for (PollingTuple tuple : pollingTuples) {
|
for (PollingTuple tuple : pollingTuples) {
|
||||||
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
||||||
fakeDetails.serverCert = tuple.computer.serverCert;
|
fakeDetails.serverCert = tuple.computer.serverCert;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
|
||||||
// This will insert the host into the database too.
|
|
||||||
runPoll(fakeDetails, true, 0);
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return false;
|
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||||
|
// This will insert the host into the database too.
|
||||||
|
runPoll(fakeDetails, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the machine is reachable, it was successful
|
// If the machine is reachable, it was successful
|
||||||
@@ -528,11 +544,6 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
||||||
// Fast poll this address first to determine if we can connect at the TCP layer
|
|
||||||
if (!fastPollIp(address)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
|
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
|
||||||
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
@@ -551,146 +562,140 @@ public class ComputerManagerService extends Service {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new active address
|
|
||||||
newDetails.activeAddress = address;
|
|
||||||
|
|
||||||
return newDetails;
|
return newDetails;
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just try to establish a TCP connection to speculatively detect a running
|
private static class ParallelPollTuple {
|
||||||
// GFE server
|
public String address;
|
||||||
private boolean fastPollIp(String address) {
|
public ComputerDetails existingDetails;
|
||||||
if (address == null) {
|
|
||||||
// Don't bother if our address is null
|
public boolean complete;
|
||||||
return false;
|
public Thread pollingThread;
|
||||||
|
public ComputerDetails returnedDetails;
|
||||||
|
|
||||||
|
public ParallelPollTuple(String address, ComputerDetails existingDetails) {
|
||||||
|
this.address = address;
|
||||||
|
this.existingDetails = existingDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket s = new Socket();
|
public void interrupt() {
|
||||||
try {
|
if (pollingThread != null) {
|
||||||
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
pollingThread.interrupt();
|
||||||
s.close();
|
}
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFastPollThread(final String address, final boolean[] info) {
|
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<String> uniqueAddresses) {
|
||||||
Thread t = new Thread() {
|
// Don't bother starting a polling thread for an address that doesn't exist
|
||||||
|
// or if the address has already been polled with an earlier tuple
|
||||||
|
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
|
||||||
|
tuple.complete = true;
|
||||||
|
tuple.returnedDetails = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple.pollingThread = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean pollRes = fastPollIp(address);
|
ComputerDetails details = tryPollIp(tuple.existingDetails, tuple.address);
|
||||||
|
|
||||||
synchronized (info) {
|
synchronized (tuple) {
|
||||||
info[0] = true; // Done
|
tuple.complete = true; // Done
|
||||||
info[1] = pollRes; // Polling result
|
tuple.returnedDetails = details; // Polling result
|
||||||
|
|
||||||
info.notify();
|
tuple.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
t.setName("Fast Poll - "+address);
|
tuple.pollingThread.setName("Parallel Poll - "+tuple.address+" - "+tuple.existingDetails.name);
|
||||||
t.start();
|
tuple.pollingThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress, final String ipv6Address) throws InterruptedException {
|
private ComputerDetails parallelPollPc(ComputerDetails details) throws InterruptedException {
|
||||||
final boolean[] remoteInfo = new boolean[2];
|
ParallelPollTuple localInfo = new ParallelPollTuple(details.localAddress, details);
|
||||||
final boolean[] localInfo = new boolean[2];
|
ParallelPollTuple manualInfo = new ParallelPollTuple(details.manualAddress, details);
|
||||||
final boolean[] manualInfo = new boolean[2];
|
ParallelPollTuple remoteInfo = new ParallelPollTuple(details.remoteAddress, details);
|
||||||
final boolean[] ipv6Info = new boolean[2];
|
ParallelPollTuple ipv6Info = new ParallelPollTuple(details.ipv6Address, details);
|
||||||
|
|
||||||
startFastPollThread(localAddress, localInfo);
|
// These must be started in order of precedence for the deduplication algorithm
|
||||||
startFastPollThread(remoteAddress, remoteInfo);
|
// to result in the correct behavior.
|
||||||
startFastPollThread(manualAddress, manualInfo);
|
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||||
startFastPollThread(ipv6Address, ipv6Info);
|
startParallelPollThread(localInfo, uniqueAddresses);
|
||||||
|
startParallelPollThread(manualInfo, uniqueAddresses);
|
||||||
|
startParallelPollThread(remoteInfo, uniqueAddresses);
|
||||||
|
startParallelPollThread(ipv6Info, uniqueAddresses);
|
||||||
|
|
||||||
// Check local first
|
try {
|
||||||
synchronized (localInfo) {
|
// Check local first
|
||||||
while (!localInfo[0]) {
|
synchronized (localInfo) {
|
||||||
localInfo.wait(500);
|
while (!localInfo.complete) {
|
||||||
|
localInfo.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localInfo.returnedDetails != null) {
|
||||||
|
localInfo.returnedDetails.activeAddress = localInfo.address;
|
||||||
|
return localInfo.returnedDetails;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localInfo[1]) {
|
// Now manual
|
||||||
return localAddress;
|
synchronized (manualInfo) {
|
||||||
}
|
while (!manualInfo.complete) {
|
||||||
}
|
manualInfo.wait();
|
||||||
|
}
|
||||||
|
|
||||||
// Now manual
|
if (manualInfo.returnedDetails != null) {
|
||||||
synchronized (manualInfo) {
|
manualInfo.returnedDetails.activeAddress = manualInfo.address;
|
||||||
while (!manualInfo[0]) {
|
return manualInfo.returnedDetails;
|
||||||
manualInfo.wait(500);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manualInfo[1]) {
|
// Now remote IPv4
|
||||||
return manualAddress;
|
synchronized (remoteInfo) {
|
||||||
}
|
while (!remoteInfo.complete) {
|
||||||
}
|
remoteInfo.wait();
|
||||||
|
}
|
||||||
|
|
||||||
// Now remote IPv4
|
if (remoteInfo.returnedDetails != null) {
|
||||||
synchronized (remoteInfo) {
|
remoteInfo.returnedDetails.activeAddress = remoteInfo.address;
|
||||||
while (!remoteInfo[0]) {
|
return remoteInfo.returnedDetails;
|
||||||
remoteInfo.wait(500);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteInfo[1]) {
|
// Now global IPv6
|
||||||
return remoteAddress;
|
synchronized (ipv6Info) {
|
||||||
}
|
while (!ipv6Info.complete) {
|
||||||
}
|
ipv6Info.wait();
|
||||||
|
}
|
||||||
|
|
||||||
// Now global IPv6
|
if (ipv6Info.returnedDetails != null) {
|
||||||
synchronized (ipv6Info) {
|
ipv6Info.returnedDetails.activeAddress = ipv6Info.address;
|
||||||
while (!ipv6Info[0]) {
|
return ipv6Info.returnedDetails;
|
||||||
ipv6Info.wait(500);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (ipv6Info[1]) {
|
|
||||||
return ipv6Address;
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
// Stop any further polling if we've found a working address or we've been
|
||||||
|
// interrupted by an attempt to stop polling.
|
||||||
|
localInfo.interrupt();
|
||||||
|
manualInfo.interrupt();
|
||||||
|
remoteInfo.interrupt();
|
||||||
|
ipv6Info.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
||||||
ComputerDetails polledDetails;
|
// Poll all addresses in parallel to speed up the process
|
||||||
|
LimeLog.info("Starting parallel poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
||||||
// Do a TCP-level connection to the HTTP server to see if it's listening.
|
ComputerDetails polledDetails = parallelPollPc(details);
|
||||||
// Do not write this address to details.activeAddress because:
|
LimeLog.info("Parallel poll for "+details.name+" returned address: "+details.activeAddress);
|
||||||
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
|
|
||||||
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
|
|
||||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
|
||||||
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress, details.ipv6Address);
|
|
||||||
LimeLog.info("Fast poll for "+details.name+" returned candidate address: "+candidateAddress);
|
|
||||||
|
|
||||||
// If no connection could be established to either IP address, there's nothing we can do
|
|
||||||
if (candidateAddress == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try using the active address from fast-poll
|
|
||||||
polledDetails = tryPollIp(details, candidateAddress);
|
|
||||||
if (polledDetails == null) {
|
|
||||||
// If that failed, try all unique addresses except what we've
|
|
||||||
// already tried
|
|
||||||
HashSet<String> uniqueAddresses = new HashSet<>();
|
|
||||||
uniqueAddresses.add(details.localAddress);
|
|
||||||
uniqueAddresses.add(details.manualAddress);
|
|
||||||
uniqueAddresses.add(details.remoteAddress);
|
|
||||||
uniqueAddresses.add(details.ipv6Address);
|
|
||||||
for (String addr : uniqueAddresses) {
|
|
||||||
if (addr == null || addr.equals(candidateAddress)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
polledDetails = tryPollIp(details, addr);
|
|
||||||
if (polledDetails != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (polledDetails != null) {
|
if (polledDetails != null) {
|
||||||
details.update(polledDetails);
|
details.update(polledDetails);
|
||||||
|
|||||||
@@ -128,6 +128,13 @@ public class CachedAppAssetLoader {
|
|||||||
try {
|
try {
|
||||||
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public class ConnectionContext {
|
|||||||
// This is the version quad from the appversion tag of /serverinfo
|
// This is the version quad from the appversion tag of /serverinfo
|
||||||
public String serverAppVersion;
|
public String serverAppVersion;
|
||||||
public String serverGfeVersion;
|
public String serverGfeVersion;
|
||||||
|
|
||||||
|
// This is the sessionUrl0 tag from /resume and /launch
|
||||||
|
public String rtspSessionUrl;
|
||||||
|
|
||||||
public int negotiatedWidth, negotiatedHeight;
|
public int negotiatedWidth, negotiatedHeight;
|
||||||
public boolean negotiatedHdr;
|
public boolean negotiatedHdr;
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class NvConnection {
|
|||||||
}
|
}
|
||||||
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
||||||
!context.streamConfig.getHevcSupported()) {
|
!context.streamConfig.getHevcSupported()) {
|
||||||
context.connListener.displayMessage("Your streaming device must support H.265 to stream at resolutions above 4K.");
|
context.connListener.displayMessage("Your streaming device must support HEVC to stream at resolutions above 4K.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||||
@@ -275,7 +275,7 @@ public class NvConnection {
|
|||||||
synchronized (MoonBridge.class) {
|
synchronized (MoonBridge.class) {
|
||||||
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
||||||
int ret = MoonBridge.startConnection(context.serverAddress,
|
int ret = MoonBridge.startConnection(context.serverAddress,
|
||||||
context.serverAppVersion, context.serverGfeVersion,
|
context.serverAppVersion, context.serverGfeVersion, context.rtspSessionUrl,
|
||||||
context.negotiatedWidth, context.negotiatedHeight,
|
context.negotiatedWidth, context.negotiatedHeight,
|
||||||
context.streamConfig.getRefreshRate(), context.streamConfig.getBitrate(),
|
context.streamConfig.getRefreshRate(), context.streamConfig.getBitrate(),
|
||||||
context.streamConfig.getMaxPacketSize(),
|
context.streamConfig.getMaxPacketSize(),
|
||||||
@@ -284,6 +284,7 @@ public class NvConnection {
|
|||||||
context.negotiatedHdr,
|
context.negotiatedHdr,
|
||||||
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
||||||
context.streamConfig.getClientRefreshRateX100(),
|
context.streamConfig.getClientRefreshRateX100(),
|
||||||
|
context.streamConfig.getEncryptionFlags(),
|
||||||
context.riKey.getEncoded(), ib.array(),
|
context.riKey.getEncoded(), ib.array(),
|
||||||
context.videoCapabilities);
|
context.videoCapabilities);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@@ -310,7 +311,14 @@ public class NvConnection {
|
|||||||
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight)
|
||||||
|
{
|
||||||
|
if (!isMonkey) {
|
||||||
|
MoonBridge.sendMouseMoveAsMousePosition(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void sendMouseButtonDown(final byte mouseButton)
|
public void sendMouseButtonDown(final byte mouseButton)
|
||||||
{
|
{
|
||||||
if (!isMonkey) {
|
if (!isMonkey) {
|
||||||
@@ -366,6 +374,12 @@ public class NvConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendUtf8Text(final String text) {
|
||||||
|
if (!isMonkey) {
|
||||||
|
MoonBridge.sendUtf8Text(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
||||||
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,6 @@ public interface NvConnectionListener {
|
|||||||
void displayTransientMessage(String message);
|
void displayTransientMessage(String message);
|
||||||
|
|
||||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||||
|
|
||||||
|
void setHdrMode(boolean enabled);
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ public class StreamConfiguration {
|
|||||||
private int hevcBitratePercentageMultiplier;
|
private int hevcBitratePercentageMultiplier;
|
||||||
private boolean enableHdr;
|
private boolean enableHdr;
|
||||||
private int attachedGamepadMask;
|
private int attachedGamepadMask;
|
||||||
|
private int encryptionFlags;
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private StreamConfiguration config = new StreamConfiguration();
|
private StreamConfiguration config = new StreamConfiguration();
|
||||||
@@ -110,7 +111,17 @@ public class StreamConfiguration {
|
|||||||
config.clientRefreshRateX100 = refreshRateX100;
|
config.clientRefreshRateX100 = refreshRateX100;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StreamConfiguration.Builder setAudioEncryption(boolean enable) {
|
||||||
|
if (enable) {
|
||||||
|
config.encryptionFlags |= MoonBridge.ENCFLG_AUDIO;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config.encryptionFlags &= ~MoonBridge.ENCFLG_AUDIO;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setAudioConfiguration(MoonBridge.AudioConfiguration audioConfig) {
|
public StreamConfiguration.Builder setAudioConfiguration(MoonBridge.AudioConfiguration audioConfig) {
|
||||||
config.audioConfiguration = audioConfig;
|
config.audioConfiguration = audioConfig;
|
||||||
return this;
|
return this;
|
||||||
@@ -211,4 +222,8 @@ public class StreamConfiguration {
|
|||||||
public int getClientRefreshRateX100() {
|
public int getClientRefreshRateX100() {
|
||||||
return clientRefreshRateX100;
|
return clientRefreshRateX100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getEncryptionFlags() {
|
||||||
|
return encryptionFlags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,11 @@ public abstract class VideoDecoderRenderer {
|
|||||||
// This is called once for each frame-start NALU. This means it will be called several times
|
// This is called once for each frame-start NALU. This means it will be called several times
|
||||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
|
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs);
|
||||||
|
|
||||||
public abstract void cleanup();
|
public abstract void cleanup();
|
||||||
|
|
||||||
public abstract int getCapabilities();
|
public abstract int getCapabilities();
|
||||||
|
|
||||||
|
public abstract void setHdrMode(boolean enabled);
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public class NvApp {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append("Name: ").append(appName).append("\n");
|
str.append("Name: ").append(appName).append("\n");
|
||||||
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
|
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
|
||||||
str.append("ID: ").append(appId).append("\n");
|
str.append("ID: ").append(appId).append("\n");
|
||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
@@ -53,6 +50,7 @@ import com.limelight.nvstream.ConnectionContext;
|
|||||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||||
|
|
||||||
import okhttp3.ConnectionPool;
|
import okhttp3.ConnectionPool;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
@@ -71,8 +69,8 @@ public class NvHTTP {
|
|||||||
// Print URL and content to logcat on debug builds
|
// Print URL and content to logcat on debug builds
|
||||||
private static boolean verbose = BuildConfig.DEBUG;
|
private static boolean verbose = BuildConfig.DEBUG;
|
||||||
|
|
||||||
public String baseUrlHttps;
|
private HttpUrl baseUrlHttps;
|
||||||
public String baseUrlHttp;
|
private HttpUrl baseUrlHttp;
|
||||||
|
|
||||||
private OkHttpClient httpClient;
|
private OkHttpClient httpClient;
|
||||||
private OkHttpClient httpClientWithReadTimeout;
|
private OkHttpClient httpClientWithReadTimeout;
|
||||||
@@ -190,22 +188,26 @@ public class NvHTTP {
|
|||||||
initializeHttpState(cryptoProvider);
|
initializeHttpState(cryptoProvider);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// The URI constructor takes care of escaping IPv6 literals
|
this.baseUrlHttp = new HttpUrl.Builder()
|
||||||
this.baseUrlHttps = new URI("https", null, address, HTTPS_PORT, null, null, null).toString();
|
.scheme("http")
|
||||||
this.baseUrlHttp = new URI("http", null, address, HTTP_PORT, null, null, null).toString();
|
.host(address)
|
||||||
} catch (URISyntaxException e) {
|
.port(HTTP_PORT)
|
||||||
// Encapsulate URISyntaxException into IOException for callers to handle more easily
|
.build();
|
||||||
|
|
||||||
|
this.baseUrlHttps = new HttpUrl.Builder()
|
||||||
|
.scheme("https")
|
||||||
|
.host(address)
|
||||||
|
.port(HTTPS_PORT)
|
||||||
|
.build();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pm = new PairingManager(this, cryptoProvider);
|
this.pm = new PairingManager(this, cryptoProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
String buildUniqueIdUuidString() {
|
static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||||
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException {
|
|
||||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||||
factory.setNamespaceAware(true);
|
factory.setNamespaceAware(true);
|
||||||
XmlPullParser xpp = factory.newPullParser();
|
XmlPullParser xpp = factory.newPullParser();
|
||||||
@@ -234,11 +236,19 @@ public class NvHTTP {
|
|||||||
eventType = xpp.next();
|
eventType = xpp.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwIfMissing) {
|
||||||
|
// We throw an XmlPullParserException here for ease of handling in all the various callers.
|
||||||
|
// We could also throw an IOException, but some callers expect those in cases where the
|
||||||
|
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
|
||||||
|
// are returning garbage XML to us, so we use XmlPullParserException instead.
|
||||||
|
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException {
|
static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||||
return getXmlString(new StringReader(str), tagname);
|
return getXmlString(new StringReader(str), tagname, throwIfMissing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
||||||
@@ -272,7 +282,7 @@ public class NvHTTP {
|
|||||||
if (serverCert != null) {
|
if (serverCert != null) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
resp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
|
resp = openHttpConnectionToString(baseUrlHttps, "serverinfo", true);
|
||||||
} catch (SSLHandshakeException e) {
|
} catch (SSLHandshakeException e) {
|
||||||
// Detect if we failed due to a server cert mismatch
|
// Detect if we failed due to a server cert mismatch
|
||||||
if (e.getCause() instanceof CertificateException) {
|
if (e.getCause() instanceof CertificateException) {
|
||||||
@@ -292,7 +302,7 @@ public class NvHTTP {
|
|||||||
catch (GfeHttpResponseException e) {
|
catch (GfeHttpResponseException e) {
|
||||||
if (e.getErrorCode() == 401) {
|
if (e.getErrorCode() == 401) {
|
||||||
// Cert validation error - fall back to HTTP
|
// Cert validation error - fall back to HTTP
|
||||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
return openHttpConnectionToString(baseUrlHttp, "serverinfo", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not a cert validation error, throw it
|
// If it's not a cert validation error, throw it
|
||||||
@@ -303,7 +313,7 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No pinned cert, so use HTTP
|
// No pinned cert, so use HTTP
|
||||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
return openHttpConnectionToString(baseUrlHttp , "serverinfo", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,21 +321,21 @@ public class NvHTTP {
|
|||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = new ComputerDetails();
|
||||||
String serverInfo = getServerInfo();
|
String serverInfo = getServerInfo();
|
||||||
|
|
||||||
details.name = getXmlString(serverInfo, "hostname");
|
details.name = getXmlString(serverInfo, "hostname", false);
|
||||||
if (details.name == null || details.name.isEmpty()) {
|
if (details.name == null || details.name.isEmpty()) {
|
||||||
details.name = "UNKNOWN";
|
details.name = "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
details.uuid = getXmlString(serverInfo, "uniqueid");
|
// UUID is mandatory to determine which machine is responding
|
||||||
details.macAddress = getXmlString(serverInfo, "mac");
|
details.uuid = getXmlString(serverInfo, "uniqueid", true);
|
||||||
details.localAddress = getXmlString(serverInfo, "LocalIP");
|
|
||||||
|
|
||||||
// This may be null, but that's okay
|
details.macAddress = getXmlString(serverInfo, "mac", false);
|
||||||
details.remoteAddress = getXmlString(serverInfo, "ExternalIP");
|
details.localAddress = getXmlString(serverInfo, "LocalIP", false);
|
||||||
|
|
||||||
|
// This is missing on on recent GFE versions
|
||||||
|
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
|
||||||
|
|
||||||
// This has some extra logic to always report unpaired if the pinned cert isn't there
|
|
||||||
details.pairState = getPairState(serverInfo);
|
details.pairState = getPairState(serverInfo);
|
||||||
|
|
||||||
details.runningGameId = getCurrentGame(serverInfo);
|
details.runningGameId = getCurrentGame(serverInfo);
|
||||||
|
|
||||||
// We could reach it so it's online
|
// We could reach it so it's online
|
||||||
@@ -357,12 +367,26 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpUrl getCompleteUrl(HttpUrl baseUrl, String path, String query) {
|
||||||
|
return baseUrl.newBuilder()
|
||||||
|
.addPathSegment(path)
|
||||||
|
.query(query)
|
||||||
|
.addQueryParameter("uniqueid", uniqueId)
|
||||||
|
.addQueryParameter("uuid", UUID.randomUUID().toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
|
||||||
|
return openHttpConnection(baseUrl, path, null, enableReadTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
// Read timeout should be enabled for any HTTP query that requires no outside action
|
// Read timeout should be enabled for any HTTP query that requires no outside action
|
||||||
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
|
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
|
||||||
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
|
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
|
||||||
// queries do not.
|
// queries do not.
|
||||||
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||||
Request request = new Request.Builder().url(url).get().build();
|
HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
|
||||||
|
Request request = new Request.Builder().url(completeUrl).get().build();
|
||||||
Response response;
|
Response response;
|
||||||
|
|
||||||
if (enableReadTimeout) {
|
if (enableReadTimeout) {
|
||||||
@@ -384,30 +408,31 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.code() == 404) {
|
if (response.code() == 404) {
|
||||||
throw new FileNotFoundException(url);
|
throw new FileNotFoundException(completeUrl.toString());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new GfeHttpResponseException(response.code(), response.message());
|
throw new GfeHttpResponseException(response.code(), response.message());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws IOException {
|
|
||||||
try {
|
|
||||||
if (verbose) {
|
|
||||||
LimeLog.info("Requesting URL: "+url);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
|
private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
|
||||||
|
return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||||
|
try {
|
||||||
|
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
|
||||||
String respString = resp.string();
|
String respString = resp.string();
|
||||||
resp.close();
|
resp.close();
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose && !path.equals("serverinfo")) {
|
||||||
LimeLog.info(url+" -> "+respString);
|
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return respString;
|
return respString;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (verbose) {
|
if (verbose && !path.equals("serverinfo")) {
|
||||||
|
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +441,8 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
|
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
return getXmlString(serverInfo, "appversion");
|
// appversion is present in all supported GFE versions
|
||||||
|
return getXmlString(serverInfo, "appversion", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
|
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
|
||||||
@@ -424,15 +450,14 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
|
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
|
||||||
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
|
// appversion is present in all supported GFE versions
|
||||||
return PairState.NOT_PAIRED;
|
return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
|
||||||
}
|
PairState.PAIRED : PairState.NOT_PAIRED;
|
||||||
|
|
||||||
return PairState.PAIRED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
|
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
|
// MaxLumaPixelsH264 wasn't present on old GFE versions
|
||||||
|
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
|
||||||
if (str != null) {
|
if (str != null) {
|
||||||
return Long.parseLong(str);
|
return Long.parseLong(str);
|
||||||
} else {
|
} else {
|
||||||
@@ -441,7 +466,8 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
|
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
// MaxLumaPixelsHEVC wasn't present on old GFE versions
|
||||||
|
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
|
||||||
if (str != null) {
|
if (str != null) {
|
||||||
return Long.parseLong(str);
|
return Long.parseLong(str);
|
||||||
} else {
|
} else {
|
||||||
@@ -458,7 +484,8 @@ public class NvHTTP {
|
|||||||
// Bit 10: HEVC Main10 4:4:4
|
// Bit 10: HEVC Main10 4:4:4
|
||||||
// Bit 11: ???
|
// Bit 11: ???
|
||||||
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
|
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
|
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||||
|
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
|
||||||
if (str != null) {
|
if (str != null) {
|
||||||
return Long.parseLong(str);
|
return Long.parseLong(str);
|
||||||
} else {
|
} else {
|
||||||
@@ -467,16 +494,18 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
|
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
return getXmlString(serverInfo, "gputype");
|
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||||
|
return getXmlString(serverInfo, "gputype", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
|
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
return getXmlString(serverInfo, "GfeVersion");
|
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||||
|
return getXmlString(serverInfo, "GfeVersion", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
|
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
|
||||||
// Only allow 4K on GFE 3.x
|
// Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
|
||||||
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion");
|
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
|
||||||
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
|
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -488,10 +517,8 @@ public class NvHTTP {
|
|||||||
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
|
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
|
||||||
// has the semantics that its name would indicate. To contain the effects of this change as much
|
// has the semantics that its name would indicate. To contain the effects of this change as much
|
||||||
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
||||||
String serverState = getXmlString(serverInfo, "state");
|
if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
|
||||||
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) {
|
return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
|
||||||
String game = getXmlString(serverInfo, "currentgame");
|
|
||||||
return Integer.parseInt(game);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -588,8 +615,8 @@ public class NvHTTP {
|
|||||||
return appList;
|
return appList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAppListRaw() throws MalformedURLException, IOException {
|
public String getAppListRaw() throws IOException {
|
||||||
return openHttpConnectionToString(baseUrlHttps + "/applist?"+buildUniqueIdUuidString(), true);
|
return openHttpConnectionToString(baseUrlHttps, "applist", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||||
@@ -598,20 +625,31 @@ public class NvHTTP {
|
|||||||
return getAppListByReader(new StringReader(getAppListRaw()));
|
return getAppListByReader(new StringReader(getAppListRaw()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
|
ResponseBody resp = openHttpConnection(baseUrlHttps, "applist", true);
|
||||||
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
|
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
|
||||||
resp.close();
|
resp.close();
|
||||||
return appList;
|
return appList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
|
||||||
|
return openHttpConnectionToString(baseUrlHttp, "pair",
|
||||||
|
"devicename=roth&updateState=1&" + additionalArguments,
|
||||||
|
enableReadTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
String executePairingChallenge() throws GfeHttpResponseException, IOException {
|
||||||
|
return openHttpConnectionToString(baseUrlHttps, "pair",
|
||||||
|
"devicename=roth&updateState=1&phrase=pairchallenge",
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
public void unpair() throws IOException {
|
public void unpair() throws IOException {
|
||||||
openHttpConnectionToString(baseUrlHttp + "/unpair?"+buildUniqueIdUuidString(), true);
|
openHttpConnectionToString(baseUrlHttp, "unpair", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getBoxArt(NvApp app) throws IOException {
|
public InputStream getBoxArt(NvApp app) throws IOException {
|
||||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() +
|
ResponseBody resp = openHttpConnection(baseUrlHttps, "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
|
||||||
"&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
|
|
||||||
return resp.byteStream();
|
return resp.byteStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,9 +704,8 @@ public class NvHTTP {
|
|||||||
enableSops = false;
|
enableSops = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String xmlStr = openHttpConnectionToString(baseUrlHttps +
|
String xmlStr = openHttpConnectionToString(baseUrlHttps, "launch",
|
||||||
"/launch?" + buildUniqueIdUuidString() +
|
"appid=" + appId +
|
||||||
"&appid=" + appId +
|
|
||||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||||
@@ -679,24 +716,35 @@ public class NvHTTP {
|
|||||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
||||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
|
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
|
||||||
false);
|
false);
|
||||||
String gameSession = getXmlString(xmlStr, "gamesession");
|
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
|
||||||
return gameSession != null && !gameSession.equals("0");
|
// sessionUrl0 will be missing for older GFE versions
|
||||||
|
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
||||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
|
String xmlStr = openHttpConnectionToString(baseUrlHttps, "resume",
|
||||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
"rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||||
"&rikeyid="+context.riKeyId +
|
"&rikeyid="+context.riKeyId +
|
||||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
|
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
|
||||||
false);
|
false);
|
||||||
String resume = getXmlString(xmlStr, "resume");
|
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
|
||||||
return Integer.parseInt(resume) != 0;
|
// sessionUrl0 will be missing for older GFE versions
|
||||||
|
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean quitApp() throws IOException, XmlPullParserException {
|
public boolean quitApp() throws IOException, XmlPullParserException {
|
||||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
|
String xmlStr = openHttpConnectionToString(baseUrlHttps, "cancel", false);
|
||||||
String cancel = getXmlString(xmlStr, "cancel");
|
if (getXmlString(xmlStr, "cancel", true).equals("0")) {
|
||||||
if (Integer.parseInt(cancel) == 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.limelight.nvstream.http;
|
package com.limelight.nvstream.http;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import org.bouncycastle.crypto.BlockCipher;
|
||||||
import javax.crypto.SecretKey;
|
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
@@ -21,7 +21,6 @@ public class PairingManager {
|
|||||||
|
|
||||||
private PrivateKey pk;
|
private PrivateKey pk;
|
||||||
private X509Certificate cert;
|
private X509Certificate cert;
|
||||||
private SecretKey aesKey;
|
|
||||||
private byte[] pemCertBytes;
|
private byte[] pemCertBytes;
|
||||||
|
|
||||||
private X509Certificate serverCert;
|
private X509Certificate serverCert;
|
||||||
@@ -68,7 +67,8 @@ public class PairingManager {
|
|||||||
|
|
||||||
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
||||||
{
|
{
|
||||||
String certText = NvHTTP.getXmlString(text, "plaincert");
|
// Plaincert may be null if another client is already trying to pair
|
||||||
|
String certText = NvHTTP.getXmlString(text, "plaincert", false);
|
||||||
if (certText != null) {
|
if (certText != null) {
|
||||||
byte[] certBytes = hexToBytes(certText);
|
byte[] certBytes = hexToBytes(certText);
|
||||||
|
|
||||||
@@ -124,43 +124,35 @@ public class PairingManager {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
|
||||||
|
|
||||||
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
|
||||||
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
|
||||||
byte[] fullDecrypted = new byte[blockRoundedSize];
|
|
||||||
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
private static byte[] performBlockCipher(BlockCipher blockCipher, byte[] input) {
|
||||||
cipher.doFinal(blockRoundedEncrypted, 0,
|
int blockSize = blockCipher.getBlockSize();
|
||||||
blockRoundedSize, fullDecrypted);
|
int blockRoundedSize = (input.length + (blockSize - 1)) & ~(blockSize - 1);
|
||||||
return fullDecrypted;
|
|
||||||
} catch (GeneralSecurityException e) {
|
byte[] blockRoundedInputData = Arrays.copyOf(input, blockRoundedSize);
|
||||||
e.printStackTrace();
|
byte[] blockRoundedOutputData = new byte[blockRoundedSize];
|
||||||
throw new RuntimeException(e);
|
|
||||||
|
for (int offset = 0; offset < blockRoundedSize; offset += blockSize) {
|
||||||
|
blockCipher.processBlock(blockRoundedInputData, offset, blockRoundedOutputData, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return blockRoundedOutputData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
private static byte[] decryptAes(byte[] encryptedData, byte[] aesKey) {
|
||||||
try {
|
BlockCipher aesEngine = new AESLightEngine();
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
aesEngine.init(false, new KeyParameter(aesKey));
|
||||||
|
return performBlockCipher(aesEngine, encryptedData);
|
||||||
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
|
||||||
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
|
||||||
return cipher.doFinal(blockRoundedData);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
private static byte[] encryptAes(byte[] plaintextData, byte[] aesKey) {
|
||||||
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
BlockCipher aesEngine = new AESLightEngine();
|
||||||
return new SecretKeySpec(aesTruncated, "AES");
|
aesEngine.init(true, new KeyParameter(aesKey));
|
||||||
|
return performBlockCipher(aesEngine, plaintextData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||||
|
return Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] concatBytes(byte[] a, byte[] b) {
|
private static byte[] concatBytes(byte[] a, byte[] b) {
|
||||||
@@ -199,16 +191,14 @@ public class PairingManager {
|
|||||||
byte[] salt = generateRandomBytes(16);
|
byte[] salt = generateRandomBytes(16);
|
||||||
|
|
||||||
// Combine the salt and pin, then create an AES key from them
|
// Combine the salt and pin, then create an AES key from them
|
||||||
byte[] saltAndPin = saltPin(salt, pin);
|
byte[] aesKey = generateAesKey(hashAlgo, saltPin(salt, pin));
|
||||||
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
|
||||||
|
|
||||||
// Send the salt and get the server cert. This doesn't have a read timeout
|
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||||
// because the user must enter the PIN before the server responds
|
// because the user must enter the PIN before the server responds
|
||||||
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
|
String getCert = http.executePairingCommand("phrase=getservercert&salt="+
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
|
||||||
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
||||||
false);
|
false);
|
||||||
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +207,7 @@ public class PairingManager {
|
|||||||
if (serverCert == null) {
|
if (serverCert == null) {
|
||||||
// Attempting to pair while another device is pairing will cause GFE
|
// Attempting to pair while another device is pairing will cause GFE
|
||||||
// to give an empty cert in the response.
|
// to give an empty cert in the response.
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.unpair();
|
||||||
return PairState.ALREADY_IN_PROGRESS;
|
return PairState.ALREADY_IN_PROGRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,16 +219,14 @@ public class PairingManager {
|
|||||||
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
||||||
|
|
||||||
// Send the encrypted challenge to the server
|
// Send the encrypted challenge to the server
|
||||||
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String challengeResp = http.executePairingCommand("clientchallenge="+bytesToHex(encryptedChallenge), true);
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
|
||||||
true);
|
http.unpair();
|
||||||
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the server's response and subsequent challenge
|
// Decode the server's response and subsequent challenge
|
||||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
|
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
|
||||||
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
||||||
|
|
||||||
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
||||||
@@ -248,23 +236,21 @@ public class PairingManager {
|
|||||||
byte[] clientSecret = generateRandomBytes(16);
|
byte[] clientSecret = generateRandomBytes(16);
|
||||||
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
||||||
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
||||||
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String secretResp = http.executePairingCommand("serverchallengeresp="+bytesToHex(challengeRespEncrypted), true);
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
|
||||||
true);
|
http.unpair();
|
||||||
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the server's signed secret
|
// Get the server's signed secret
|
||||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
|
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
|
||||||
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
||||||
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
||||||
|
|
||||||
// Ensure the authenticity of the data
|
// Ensure the authenticity of the data
|
||||||
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.unpair();
|
||||||
|
|
||||||
// Looks like a MITM
|
// Looks like a MITM
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
@@ -274,7 +260,7 @@ public class PairingManager {
|
|||||||
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
||||||
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
http.unpair();
|
||||||
|
|
||||||
// Probably got the wrong PIN
|
// Probably got the wrong PIN
|
||||||
return PairState.PIN_WRONG;
|
return PairState.PIN_WRONG;
|
||||||
@@ -282,19 +268,16 @@ public class PairingManager {
|
|||||||
|
|
||||||
// Send the server our signed secret
|
// Send the server our signed secret
|
||||||
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
||||||
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
String clientSecretResp = http.executePairingCommand("clientpairingsecret="+bytesToHex(clientPairingSecret), true);
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
|
||||||
true);
|
http.unpair();
|
||||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the initial challenge (seems neccessary for us to show as paired)
|
// Do the initial challenge (seems necessary for us to show as paired)
|
||||||
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
|
String pairChallenge = http.executePairingChallenge();
|
||||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
|
||||||
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
http.unpair();
|
||||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,18 @@ public class MoonBridge {
|
|||||||
public static final int VIDEO_FORMAT_MASK_H264 = 0x00FF;
|
public static final int VIDEO_FORMAT_MASK_H264 = 0x00FF;
|
||||||
public static final int VIDEO_FORMAT_MASK_H265 = 0xFF00;
|
public static final int VIDEO_FORMAT_MASK_H265 = 0xFF00;
|
||||||
|
|
||||||
|
public static final int ENCFLG_NONE = 0;
|
||||||
|
public static final int ENCFLG_AUDIO = 1;
|
||||||
|
public static final int ENCFLG_ALL = 0xFFFFFFFF;
|
||||||
|
|
||||||
public static final int BUFFER_TYPE_PICDATA = 0;
|
public static final int BUFFER_TYPE_PICDATA = 0;
|
||||||
public static final int BUFFER_TYPE_SPS = 1;
|
public static final int BUFFER_TYPE_SPS = 1;
|
||||||
public static final int BUFFER_TYPE_PPS = 2;
|
public static final int BUFFER_TYPE_PPS = 2;
|
||||||
public static final int BUFFER_TYPE_VPS = 3;
|
public static final int BUFFER_TYPE_VPS = 3;
|
||||||
|
|
||||||
|
public static final int FRAME_TYPE_PFRAME = 0;
|
||||||
|
public static final int FRAME_TYPE_IDR = 1;
|
||||||
|
|
||||||
public static final int CAPABILITY_DIRECT_SUBMIT = 1;
|
public static final int CAPABILITY_DIRECT_SUBMIT = 1;
|
||||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC = 2;
|
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC = 2;
|
||||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC = 4;
|
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC = 4;
|
||||||
@@ -37,6 +44,7 @@ public class MoonBridge {
|
|||||||
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
|
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
|
||||||
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
|
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
|
||||||
public static final int ML_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
|
public static final int ML_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
|
||||||
|
public static final int ML_ERROR_PROTECTED_CONTENT = -103;
|
||||||
|
|
||||||
public static final int ML_PORT_INDEX_TCP_47984 = 0;
|
public static final int ML_PORT_INDEX_TCP_47984 = 0;
|
||||||
public static final int ML_PORT_INDEX_TCP_47989 = 1;
|
public static final int ML_PORT_INDEX_TCP_47989 = 1;
|
||||||
@@ -148,12 +156,12 @@ public class MoonBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
|
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||||
int decodeUnitType, int frameNumber,
|
int frameNumber, int frameType,
|
||||||
long receiveTimeMs, long enqueueTimeMs) {
|
long receiveTimeMs, long enqueueTimeMs) {
|
||||||
if (videoRenderer != null) {
|
if (videoRenderer != null) {
|
||||||
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
||||||
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
|
decodeUnitType, frameNumber, frameType, receiveTimeMs, enqueueTimeMs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return DR_OK;
|
return DR_OK;
|
||||||
@@ -235,6 +243,12 @@ public class MoonBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void bridgeClSetHdrMode(boolean enabled) {
|
||||||
|
if (connectionListener != null) {
|
||||||
|
connectionListener.setHdrMode(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void setupBridge(VideoDecoderRenderer videoRenderer, AudioRenderer audioRenderer, NvConnectionListener connectionListener) {
|
public static void setupBridge(VideoDecoderRenderer videoRenderer, AudioRenderer audioRenderer, NvConnectionListener connectionListener) {
|
||||||
MoonBridge.videoRenderer = videoRenderer;
|
MoonBridge.videoRenderer = videoRenderer;
|
||||||
MoonBridge.audioRenderer = audioRenderer;
|
MoonBridge.audioRenderer = audioRenderer;
|
||||||
@@ -248,12 +262,14 @@ public class MoonBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static native int startConnection(String address, String appVersion, String gfeVersion,
|
public static native int startConnection(String address, String appVersion, String gfeVersion,
|
||||||
|
String rtspSessionUrl,
|
||||||
int width, int height, int fps,
|
int width, int height, int fps,
|
||||||
int bitrate, int packetSize, int streamingRemotely,
|
int bitrate, int packetSize, int streamingRemotely,
|
||||||
int audioConfiguration, boolean supportsHevc,
|
int audioConfiguration, boolean supportsHevc,
|
||||||
boolean enableHdr,
|
boolean enableHdr,
|
||||||
int hevcBitratePercentageMultiplier,
|
int hevcBitratePercentageMultiplier,
|
||||||
int clientRefreshRateX100,
|
int clientRefreshRateX100,
|
||||||
|
int encryptionFlags,
|
||||||
byte[] riAesKey, byte[] riAesIv,
|
byte[] riAesKey, byte[] riAesIv,
|
||||||
int videoCapabilities);
|
int videoCapabilities);
|
||||||
|
|
||||||
@@ -265,6 +281,8 @@ public class MoonBridge {
|
|||||||
|
|
||||||
public static native void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight);
|
public static native void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight);
|
||||||
|
|
||||||
|
public static native void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight);
|
||||||
|
|
||||||
public static native void sendMouseButton(byte buttonEvent, byte mouseButton);
|
public static native void sendMouseButton(byte buttonEvent, byte mouseButton);
|
||||||
|
|
||||||
public static native void sendMultiControllerInput(short controllerNumber,
|
public static native void sendMultiControllerInput(short controllerNumber,
|
||||||
@@ -284,6 +302,8 @@ public class MoonBridge {
|
|||||||
|
|
||||||
public static native void sendMouseHighResScroll(short scrollAmount);
|
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||||
|
|
||||||
|
public static native void sendUtf8Text(String text);
|
||||||
|
|
||||||
public static native String getStageName(int stage);
|
public static native String getStageName(int stage);
|
||||||
|
|
||||||
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
||||||
@@ -300,5 +320,8 @@ public class MoonBridge {
|
|||||||
|
|
||||||
public static native String stringifyPortFlags(int portFlags, String separator);
|
public static native String stringifyPortFlags(int portFlags, String separator);
|
||||||
|
|
||||||
|
// The RTT is in the top 32 bits, and the RTT variance is in the bottom 32 bits
|
||||||
|
public static native long getEstimatedRttInfo();
|
||||||
|
|
||||||
public static native void init();
|
public static native void init();
|
||||||
}
|
}
|
||||||
@@ -88,15 +88,15 @@ public class AddComputerManually extends Activity {
|
|||||||
|
|
||||||
// Couldn't find a matching interface
|
// Couldn't find a matching interface
|
||||||
return true;
|
return true;
|
||||||
} catch (SocketException e) {
|
} catch (Exception e) {
|
||||||
|
// Catch all exceptions because some broken Android devices
|
||||||
|
// will throw an NPE from inside getNetworkInterfaces().
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doAddPc(String host) {
|
private void doAddPc(String host) throws InterruptedException {
|
||||||
boolean wrongSiteLocal = false;
|
boolean wrongSiteLocal = false;
|
||||||
boolean success;
|
boolean success;
|
||||||
int portTestResult;
|
int portTestResult;
|
||||||
@@ -108,12 +108,18 @@ public class AddComputerManually extends Activity {
|
|||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = new ComputerDetails();
|
||||||
details.manualAddress = host;
|
details.manualAddress = host;
|
||||||
success = managerBinder.addComputerBlocking(details);
|
success = managerBinder.addComputerBlocking(details);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Propagate the InterruptedException to the caller for proper handling
|
||||||
|
dialog.dismiss();
|
||||||
|
throw e;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
|
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
|
||||||
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the SpinnerDialog open while testing connectivity
|
||||||
if (!success){
|
if (!success){
|
||||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||||
}
|
}
|
||||||
@@ -162,15 +168,12 @@ public class AddComputerManually extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while (!isInterrupted()) {
|
while (!isInterrupted()) {
|
||||||
String computer;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
computer = computersToAdd.take();
|
String computer = computersToAdd.take();
|
||||||
|
doAddPc(computer);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
doAddPc(computer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -184,7 +187,14 @@ public class AddComputerManually extends Activity {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
addThread.join();
|
addThread.join();
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
addThread = null;
|
addThread = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||||
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||||
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
||||||
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
private static final String LEGACY_DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||||
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
|
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
|
||||||
private static final String ENABLE_PIP_PREF_STRING = "checkbox_enable_pip";
|
private static final String ENABLE_PIP_PREF_STRING = "checkbox_enable_pip";
|
||||||
private static final String ENABLE_PERF_OVERLAY_STRING = "checkbox_enable_perf_overlay";
|
private static final String ENABLE_PERF_OVERLAY_STRING = "checkbox_enable_perf_overlay";
|
||||||
@@ -43,6 +43,8 @@ public class PreferenceConfiguration {
|
|||||||
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
|
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
|
||||||
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
||||||
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
||||||
|
private static final String FRAME_PACING_PREF_STRING = "frame_pacing";
|
||||||
|
private static final String ABSOLUTE_MOUSE_MODE_PREF_STRING = "checkbox_absolute_mouse_mode";
|
||||||
|
|
||||||
static final String DEFAULT_RESOLUTION = "1280x720";
|
static final String DEFAULT_RESOLUTION = "1280x720";
|
||||||
static final String DEFAULT_FPS = "60";
|
static final String DEFAULT_FPS = "60";
|
||||||
@@ -50,7 +52,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_SOPS = true;
|
private static final boolean DEFAULT_SOPS = true;
|
||||||
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
||||||
private static final boolean DEFAULT_HOST_AUDIO = false;
|
private static final boolean DEFAULT_HOST_AUDIO = false;
|
||||||
private static final int DEFAULT_DEADZONE = 15;
|
private static final int DEFAULT_DEADZONE = 7;
|
||||||
private static final int DEFAULT_OPACITY = 90;
|
private static final int DEFAULT_OPACITY = 90;
|
||||||
public static final String DEFAULT_LANGUAGE = "default";
|
public static final String DEFAULT_LANGUAGE = "default";
|
||||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||||
@@ -58,7 +60,6 @@ public class PreferenceConfiguration {
|
|||||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||||
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
||||||
private static final boolean ONLY_L3_R3_DEFAULT = false;
|
private static final boolean ONLY_L3_R3_DEFAULT = false;
|
||||||
private static final boolean DEFAULT_DISABLE_FRAME_DROP = false;
|
|
||||||
private static final boolean DEFAULT_ENABLE_HDR = false;
|
private static final boolean DEFAULT_ENABLE_HDR = false;
|
||||||
private static final boolean DEFAULT_ENABLE_PIP = false;
|
private static final boolean DEFAULT_ENABLE_PIP = false;
|
||||||
private static final boolean DEFAULT_ENABLE_PERF_OVERLAY = false;
|
private static final boolean DEFAULT_ENABLE_PERF_OVERLAY = false;
|
||||||
@@ -72,11 +73,18 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
|
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
|
||||||
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
||||||
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
||||||
|
private static final String DEFAULT_FRAME_PACING = "latency";
|
||||||
|
private static final boolean DEFAULT_ABSOLUTE_MOUSE_MODE = false;
|
||||||
|
|
||||||
public static final int FORCE_H265_ON = -1;
|
public static final int FORCE_H265_ON = -1;
|
||||||
public static final int AUTOSELECT_H265 = 0;
|
public static final int AUTOSELECT_H265 = 0;
|
||||||
public static final int FORCE_H265_OFF = 1;
|
public static final int FORCE_H265_OFF = 1;
|
||||||
|
|
||||||
|
public static final int FRAME_PACING_MIN_LATENCY = 0;
|
||||||
|
public static final int FRAME_PACING_BALANCED = 1;
|
||||||
|
public static final int FRAME_PACING_CAP_FPS = 2;
|
||||||
|
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
|
||||||
|
|
||||||
public static final String RES_360P = "640x360";
|
public static final String RES_360P = "640x360";
|
||||||
public static final String RES_480P = "854x480";
|
public static final String RES_480P = "854x480";
|
||||||
public static final String RES_720P = "1280x720";
|
public static final String RES_720P = "1280x720";
|
||||||
@@ -95,7 +103,6 @@ public class PreferenceConfiguration {
|
|||||||
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||||
public boolean onscreenController;
|
public boolean onscreenController;
|
||||||
public boolean onlyL3R3;
|
public boolean onlyL3R3;
|
||||||
public boolean disableFrameDrop;
|
|
||||||
public boolean enableHdr;
|
public boolean enableHdr;
|
||||||
public boolean enablePip;
|
public boolean enablePip;
|
||||||
public boolean enablePerfOverlay;
|
public boolean enablePerfOverlay;
|
||||||
@@ -108,6 +115,8 @@ public class PreferenceConfiguration {
|
|||||||
public boolean vibrateFallbackToDevice;
|
public boolean vibrateFallbackToDevice;
|
||||||
public boolean touchscreenTrackpad;
|
public boolean touchscreenTrackpad;
|
||||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||||
|
public int framePacing;
|
||||||
|
public boolean absoluteMouseMode;
|
||||||
|
|
||||||
public static boolean isNativeResolution(int width, int height) {
|
public static boolean isNativeResolution(int width, int height) {
|
||||||
// It's not a native resolution if it matches an existing resolution option
|
// It's not a native resolution if it matches an existing resolution option
|
||||||
@@ -264,6 +273,37 @@ public class PreferenceConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getFramePacingValue(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
|
// Migrate legacy never drop frames option to the new location
|
||||||
|
if (prefs.contains(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)) {
|
||||||
|
boolean legacyNeverDropFrames = prefs.getBoolean(LEGACY_DISABLE_FRAME_DROP_PREF_STRING, false);
|
||||||
|
prefs.edit()
|
||||||
|
.remove(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)
|
||||||
|
.putString(FRAME_PACING_PREF_STRING, legacyNeverDropFrames ? "balanced" : "latency")
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
String str = prefs.getString(FRAME_PACING_PREF_STRING, DEFAULT_FRAME_PACING);
|
||||||
|
if (str.equals("latency")) {
|
||||||
|
return FRAME_PACING_MIN_LATENCY;
|
||||||
|
}
|
||||||
|
else if (str.equals("balanced")) {
|
||||||
|
return FRAME_PACING_BALANCED;
|
||||||
|
}
|
||||||
|
else if (str.equals("cap-fps")) {
|
||||||
|
return FRAME_PACING_CAP_FPS;
|
||||||
|
}
|
||||||
|
else if (str.equals("smoothness")) {
|
||||||
|
return FRAME_PACING_MAX_SMOOTHNESS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Should never get here
|
||||||
|
return FRAME_PACING_MIN_LATENCY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void resetStreamingSettings(Context context) {
|
public static void resetStreamingSettings(Context context) {
|
||||||
// We consider resolution, FPS, bitrate, HDR, and video format as "streaming settings" here
|
// We consider resolution, FPS, bitrate, HDR, and video format as "streaming settings" here
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
@@ -279,6 +319,13 @@ public class PreferenceConfiguration {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
||||||
|
// This particular Shield TV firmware crashes when using HDR
|
||||||
|
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
||||||
|
return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA") &&
|
||||||
|
Build.FINGERPRINT.contains("PPR1.180610.011/4079208_2235.1395");
|
||||||
|
}
|
||||||
|
|
||||||
public static PreferenceConfiguration readPreferences(Context context) {
|
public static PreferenceConfiguration readPreferences(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||||
@@ -387,6 +434,7 @@ public class PreferenceConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.videoFormat = getVideoFormatValue(context);
|
config.videoFormat = getVideoFormatValue(context);
|
||||||
|
config.framePacing = getFramePacingValue(context);
|
||||||
|
|
||||||
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
||||||
|
|
||||||
@@ -404,8 +452,7 @@ public class PreferenceConfiguration {
|
|||||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||||
config.disableFrameDrop = prefs.getBoolean(DISABLE_FRAME_DROP_PREF_STRING, DEFAULT_DISABLE_FRAME_DROP);
|
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR) && !isShieldAtvFirmwareWithBrokenHdr();
|
||||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
|
|
||||||
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
||||||
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
||||||
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
||||||
@@ -417,6 +464,7 @@ public class PreferenceConfiguration {
|
|||||||
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
||||||
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
||||||
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
||||||
|
config.absoluteMouseMode = prefs.getBoolean(ABSOLUTE_MOUSE_MODE_PREF_STRING, DEFAULT_ABSOLUTE_MOUSE_MODE);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,28 @@ package com.limelight.preferences;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
|
import android.view.DisplayCutout;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.PcView;
|
import com.limelight.PcView;
|
||||||
@@ -28,15 +33,19 @@ import com.limelight.binding.video.MediaCodecHelper;
|
|||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class StreamSettings extends Activity {
|
public class StreamSettings extends Activity {
|
||||||
private PreferenceConfiguration previousPrefs;
|
private PreferenceConfiguration previousPrefs;
|
||||||
|
|
||||||
|
// HACK for Android 9
|
||||||
|
static DisplayCutout displayCutoutP;
|
||||||
|
|
||||||
void reloadSettings() {
|
void reloadSettings() {
|
||||||
getFragmentManager().beginTransaction().replace(
|
getFragmentManager().beginTransaction().replace(
|
||||||
R.id.stream_settings, new SettingsFragment()
|
R.id.stream_settings, new SettingsFragment()
|
||||||
).commit();
|
).commitAllowingStateLoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,11 +57,28 @@ public class StreamSettings extends Activity {
|
|||||||
UiHelper.setLocale(this);
|
UiHelper.setLocale(this);
|
||||||
|
|
||||||
setContentView(R.layout.activity_stream_settings);
|
setContentView(R.layout.activity_stream_settings);
|
||||||
reloadSettings();
|
|
||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
// We have to use this hack on Android 9 because we don't have Display.getCutout()
|
||||||
|
// which was added in Android 10.
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
|
||||||
|
// Insets can be null when the activity is recreated on screen rotation
|
||||||
|
// https://stackoverflow.com/questions/61241255/windowinsets-getdisplaycutout-is-null-everywhere-except-within-onattachedtowindo
|
||||||
|
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||||
|
if (insets != null) {
|
||||||
|
displayCutoutP = insets.getDisplayCutout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
finish();
|
finish();
|
||||||
@@ -76,10 +102,20 @@ public class StreamSettings extends Activity {
|
|||||||
pref.setValue(value);
|
pref.setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight) {
|
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight, boolean insetsRemoved) {
|
||||||
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
|
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
|
||||||
|
|
||||||
String newName = getResources().getString(R.string.resolution_prefix_native) + " ("+nativeWidth+"x"+nativeHeight+")";
|
String newName;
|
||||||
|
|
||||||
|
if (insetsRemoved) {
|
||||||
|
newName = getResources().getString(R.string.resolution_prefix_native_fullscreen);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newName = getResources().getString(R.string.resolution_prefix_native);
|
||||||
|
}
|
||||||
|
|
||||||
|
newName += " ("+nativeWidth+"x"+nativeHeight+")";
|
||||||
|
|
||||||
String newValue = nativeWidth+"x"+nativeHeight;
|
String newValue = nativeWidth+"x"+nativeHeight;
|
||||||
|
|
||||||
CharSequence[] values = pref.getEntryValues();
|
CharSequence[] values = pref.getEntryValues();
|
||||||
@@ -173,7 +209,7 @@ public class StreamSettings extends Activity {
|
|||||||
|
|
||||||
// hide on-screen controls category on non touch screen devices
|
// hide on-screen controls category on non touch screen devices
|
||||||
if (!getActivity().getPackageManager().
|
if (!getActivity().getPackageManager().
|
||||||
hasSystemFeature("android.hardware.touchscreen")) {
|
hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
|
||||||
{
|
{
|
||||||
PreferenceCategory category =
|
PreferenceCategory category =
|
||||||
(PreferenceCategory) findPreference("category_onscreen_controls");
|
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||||
@@ -187,6 +223,15 @@ public class StreamSettings extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide remote desktop mouse mode on pre-Oreo (which doesn't have pointer capture)
|
||||||
|
// and NVIDIA SHIELD devices (which support raw mouse input in pointer capture mode)
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||||
|
getActivity().getPackageManager().hasSystemFeature("com.nvidia.feature.shield")) {
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_input_settings");
|
||||||
|
category.removePreference(findPreference("checkbox_absolute_mouse_mode"));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
||||||
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||||
@@ -197,6 +242,13 @@ public class StreamSettings extends Activity {
|
|||||||
category.removePreference(findPreference("checkbox_enable_pip"));
|
category.removePreference(findPreference("checkbox_enable_pip"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire TV apps are not allowed to use WebViews or browsers, so hide the Help category
|
||||||
|
/*if (getActivity().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_help");
|
||||||
|
screen.removePreference(category);
|
||||||
|
}*/
|
||||||
|
|
||||||
// Remove the vibration options if the device can't vibrate
|
// Remove the vibration options if the device can't vibrate
|
||||||
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
|
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
|
||||||
PreferenceCategory category =
|
PreferenceCategory category =
|
||||||
@@ -218,6 +270,38 @@ public class StreamSettings extends Activity {
|
|||||||
|
|
||||||
int maxSupportedResW = 0;
|
int maxSupportedResW = 0;
|
||||||
|
|
||||||
|
// Add a native resolution with any insets included for users that don't want content
|
||||||
|
// behind the notch of their display
|
||||||
|
boolean hasInsets = false;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
DisplayCutout cutout;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
// Use the much nicer Display.getCutout() API on Android 10+
|
||||||
|
cutout = display.getCutout();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Android 9 only
|
||||||
|
cutout = displayCutoutP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cutout != null) {
|
||||||
|
int widthInsets = cutout.getSafeInsetLeft() + cutout.getSafeInsetRight();
|
||||||
|
int heightInsets = cutout.getSafeInsetBottom() + cutout.getSafeInsetTop();
|
||||||
|
|
||||||
|
if (widthInsets != 0 || heightInsets != 0) {
|
||||||
|
DisplayMetrics metrics = new DisplayMetrics();
|
||||||
|
display.getRealMetrics(metrics);
|
||||||
|
|
||||||
|
int width = Math.max(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||||
|
int height = Math.min(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||||
|
|
||||||
|
addNativeResolutionEntry(width, height, false);
|
||||||
|
hasInsets = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Always allow resolutions that are smaller or equal to the active
|
// Always allow resolutions that are smaller or equal to the active
|
||||||
// display resolution because decoders can report total non-sense to us.
|
// display resolution because decoders can report total non-sense to us.
|
||||||
// For example, a p201 device reports:
|
// For example, a p201 device reports:
|
||||||
@@ -233,7 +317,12 @@ public class StreamSettings extends Activity {
|
|||||||
int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||||
int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||||
|
|
||||||
addNativeResolutionEntry(width, height);
|
// Some TVs report strange values here, so let's avoid native resolutions on a TV
|
||||||
|
// unless they report greater than 4K resolutions.
|
||||||
|
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) ||
|
||||||
|
(width > 3840 || height > 2160)) {
|
||||||
|
addNativeResolutionEntry(width, height, hasInsets);
|
||||||
|
}
|
||||||
|
|
||||||
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
|
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
|
||||||
maxSupportedResW = 3840;
|
maxSupportedResW = 3840;
|
||||||
@@ -334,11 +423,29 @@ public class StreamSettings extends Activity {
|
|||||||
// Never remove 720p
|
// Never remove 720p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
// On Android 4.2 and later, we can get the true metrics via the
|
||||||
|
// getRealMetrics() function (unlike the lies that getWidth() and getHeight()
|
||||||
|
// tell to us).
|
||||||
|
DisplayMetrics metrics = new DisplayMetrics();
|
||||||
|
getActivity().getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
|
||||||
|
int width = Math.max(metrics.widthPixels, metrics.heightPixels);
|
||||||
|
int height = Math.min(metrics.widthPixels, metrics.heightPixels);
|
||||||
|
addNativeResolutionEntry(width, height, false);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
|
// On Android 4.1, we have to resort to reflection to invoke hidden APIs
|
||||||
|
// to get the real screen dimensions.
|
||||||
Display display = getActivity().getWindowManager().getDefaultDisplay();
|
Display display = getActivity().getWindowManager().getDefaultDisplay();
|
||||||
int width = Math.max(display.getWidth(), display.getHeight());
|
try {
|
||||||
int height = Math.min(display.getWidth(), display.getHeight());
|
Method getRawHeightFunc = Display.class.getMethod("getRawHeight");
|
||||||
addNativeResolutionEntry(width, height);
|
Method getRawWidthFunc = Display.class.getMethod("getRawWidth");
|
||||||
|
int width = (Integer) getRawWidthFunc.invoke(display);
|
||||||
|
int height = (Integer) getRawHeightFunc.invoke(display);
|
||||||
|
addNativeResolutionEntry(Math.max(width, height), Math.min(width, height), false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
|
if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
|
||||||
@@ -439,6 +546,15 @@ public class StreamSettings extends Activity {
|
|||||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||||
category.removePreference(findPreference("checkbox_enable_hdr"));
|
category.removePreference(findPreference("checkbox_enable_hdr"));
|
||||||
}
|
}
|
||||||
|
else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) {
|
||||||
|
LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware");
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||||
|
CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr");
|
||||||
|
hdrPref.setEnabled(false);
|
||||||
|
hdrPref.setChecked(false);
|
||||||
|
hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a listener to the FPS and resolution preference
|
// Add a listener to the FPS and resolution preference
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.limelight.preferences;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import com.limelight.utils.HelpLauncher;
|
||||||
|
|
||||||
|
public class WebLauncherPreference extends Preference {
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initialize(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebLauncherPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initialize(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
initialize(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize(AttributeSet attrs) {
|
||||||
|
if (attrs == null) {
|
||||||
|
throw new IllegalStateException("WebLauncherPreference must have attributes!");
|
||||||
|
}
|
||||||
|
|
||||||
|
url = attrs.getAttributeValue(null, "url");
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalStateException("WebLauncherPreference must have 'url' attribute!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
HelpLauncher.launchUrl(getContext(), url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import android.net.Uri;
|
|||||||
import com.limelight.HelpActivity;
|
import com.limelight.HelpActivity;
|
||||||
|
|
||||||
public class HelpLauncher {
|
public class HelpLauncher {
|
||||||
private static void launchUrl(Context context, String url) {
|
public static void launchUrl(Context context, String url) {
|
||||||
// Try to launch the default browser
|
// Try to launch the default browser
|
||||||
try {
|
try {
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ import java.security.cert.CertificateEncodingException;
|
|||||||
public class ServerHelper {
|
public class ServerHelper {
|
||||||
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
||||||
|
|
||||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
public static String getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||||
|
if (computer.activeAddress == null) {
|
||||||
|
throw new IOException("No active address for "+computer.name);
|
||||||
|
}
|
||||||
return computer.activeAddress;
|
return computer.activeAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ public class ServerHelper {
|
|||||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||||
Intent intent = new Intent(parent, Game.class);
|
Intent intent = new Intent(parent, Game.class);
|
||||||
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress);
|
||||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||||
@@ -72,8 +75,7 @@ public class ServerHelper {
|
|||||||
|
|
||||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
|
||||||
Toast.makeText(parent, parent.getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
Toast.makeText(parent, parent.getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class ShortcutHelper {
|
|||||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||||
private void reapShortcutsForDynamicAdd() {
|
private void reapShortcutsForDynamicAdd() {
|
||||||
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
||||||
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
while (!dynamicShortcuts.isEmpty() && dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||||
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
|
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
|
||||||
for (ShortcutInfo scut : dynamicShortcuts) {
|
for (ShortcutInfo scut : dynamicShortcuts) {
|
||||||
if (maxRankShortcut.getRank() < scut.getRank()) {
|
if (maxRankShortcut.getRank() < scut.getRank()) {
|
||||||
@@ -118,8 +118,16 @@ public class ShortcutHelper {
|
|||||||
// To avoid a random carousel of shortcuts popping in and out based on polling status,
|
// 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
|
// we only add shortcuts if it's not at the limit or the user made a conscious action
|
||||||
// to interact with this PC.
|
// to interact with this PC.
|
||||||
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
|
||||||
|
if (forceAdd) {
|
||||||
|
// This should free an entry for us to add one below
|
||||||
reapShortcutsForDynamicAdd();
|
reapShortcutsForDynamicAdd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still need to check the maximum shortcut count even after reaping,
|
||||||
|
// because there's a possibility that it could be zero.
|
||||||
|
if (sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||||
|
// Add a shortcut if there is room
|
||||||
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.limelight.utils;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.app.GameManager;
|
||||||
|
import android.app.GameState;
|
||||||
import android.app.UiModeManager;
|
import android.app.UiModeManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -13,6 +15,7 @@ import android.view.View;
|
|||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.limelight.Game;
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
@@ -24,6 +27,39 @@ public class UiHelper {
|
|||||||
private static final int TV_VERTICAL_PADDING_DP = 15;
|
private static final int TV_VERTICAL_PADDING_DP = 15;
|
||||||
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
||||||
|
|
||||||
|
private static void setGameModeStatus(Context context, boolean streaming, boolean loading, boolean interruptible) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
GameManager gameManager = context.getSystemService(GameManager.class);
|
||||||
|
|
||||||
|
if (streaming) {
|
||||||
|
gameManager.setGameState(new GameState(loading, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gameManager.setGameState(new GameState(loading, GameState.MODE_NONE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamConnecting(Context context) {
|
||||||
|
setGameModeStatus(context, true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamConnected(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamEnteringPiP(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamExitingPiP(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamEnded(Context context) {
|
||||||
|
setGameModeStatus(context, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setLocale(Activity activity)
|
public static void setLocale(Activity activity)
|
||||||
{
|
{
|
||||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||||
@@ -68,6 +104,9 @@ public class UiHelper {
|
|||||||
View rootView = activity.findViewById(android.R.id.content);
|
View rootView = activity.findViewById(android.R.id.content);
|
||||||
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
||||||
|
|
||||||
|
// Set GameState.MODE_NONE initially for all activities
|
||||||
|
setGameModeStatus(activity, false, false, false);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
// Allow this non-streaming activity to layout under notches.
|
// Allow this non-streaming activity to layout under notches.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
|
|||||||
moonlight-common-c/src/LinkedBlockingQueue.c \
|
moonlight-common-c/src/LinkedBlockingQueue.c \
|
||||||
moonlight-common-c/src/Misc.c \
|
moonlight-common-c/src/Misc.c \
|
||||||
moonlight-common-c/src/Platform.c \
|
moonlight-common-c/src/Platform.c \
|
||||||
|
moonlight-common-c/src/PlatformCrypto.c \
|
||||||
moonlight-common-c/src/PlatformSockets.c \
|
moonlight-common-c/src/PlatformSockets.c \
|
||||||
moonlight-common-c/src/RtpFecQueue.c \
|
moonlight-common-c/src/RtpAudioQueue.c \
|
||||||
moonlight-common-c/src/RtpReorderQueue.c \
|
moonlight-common-c/src/RtpVideoQueue.c \
|
||||||
moonlight-common-c/src/RtspConnection.c \
|
moonlight-common-c/src/RtspConnection.c \
|
||||||
moonlight-common-c/src/RtspParser.c \
|
moonlight-common-c/src/RtspParser.c \
|
||||||
moonlight-common-c/src/SdpGenerator.c \
|
moonlight-common-c/src/SdpGenerator.c \
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
|
||||||
|
OUTPUT_DIR=~/openssl
|
||||||
|
|
||||||
|
BASE_ARGS="no-shared no-ssl3 no-stdio no-engine no-hw"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
./Configure android-arm $BASE_ARGS -D__ANDROID_API__=16
|
||||||
|
make clean
|
||||||
|
make build_libs -j`nproc`
|
||||||
|
cp lib*.a $OUTPUT_DIR/armeabi-v7a/
|
||||||
|
|
||||||
|
./Configure android-arm64 $BASE_ARGS -D__ANDROID_API__=21
|
||||||
|
make clean
|
||||||
|
make build_libs -j`nproc`
|
||||||
|
cp lib*.a $OUTPUT_DIR/arm64-v8a/
|
||||||
|
|
||||||
|
./Configure android-x86 $BASE_ARGS -D__ANDROID_API__=16
|
||||||
|
make clean
|
||||||
|
make build_libs -j`nproc`
|
||||||
|
cp lib*.a $OUTPUT_DIR/x86/
|
||||||
|
|
||||||
|
./Configure android-x86_64 $BASE_ARGS -D__ANDROID_API__=21
|
||||||
|
make clean
|
||||||
|
make build_libs -j`nproc`
|
||||||
|
cp lib*.a $OUTPUT_DIR/x86_64/
|
||||||
|
cp -R include/ $OUTPUT_DIR/include
|
||||||
@@ -32,6 +32,7 @@ static jmethodID BridgeClConnectionStartedMethod;
|
|||||||
static jmethodID BridgeClConnectionTerminatedMethod;
|
static jmethodID BridgeClConnectionTerminatedMethod;
|
||||||
static jmethodID BridgeClRumbleMethod;
|
static jmethodID BridgeClRumbleMethod;
|
||||||
static jmethodID BridgeClConnectionStatusUpdateMethod;
|
static jmethodID BridgeClConnectionStatusUpdateMethod;
|
||||||
|
static jmethodID BridgeClSetHdrModeMethod;
|
||||||
static jbyteArray DecodedFrameBuffer;
|
static jbyteArray DecodedFrameBuffer;
|
||||||
static jshortArray DecodedAudioBuffer;
|
static jshortArray DecodedAudioBuffer;
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
|||||||
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
||||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
|
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIIJJ)I");
|
||||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||||
@@ -92,6 +93,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
|||||||
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
||||||
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
||||||
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
||||||
|
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z)V");
|
||||||
}
|
}
|
||||||
|
|
||||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||||
@@ -157,8 +159,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
|||||||
|
|
||||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||||
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
||||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
|
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||||
(jlong)decodeUnit->enqueueTimeMs);
|
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||||
if ((*env)->ExceptionCheck(env)) {
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
// We will crash here
|
// We will crash here
|
||||||
(*JVM)->DetachCurrentThread(JVM);
|
(*JVM)->DetachCurrentThread(JVM);
|
||||||
@@ -178,7 +180,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
|||||||
|
|
||||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||||
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
||||||
decodeUnit->frameNumber,
|
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||||
if ((*env)->ExceptionCheck(env)) {
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
// We will crash here
|
// We will crash here
|
||||||
@@ -326,6 +328,16 @@ void BridgeClConnectionStatusUpdate(int connectionStatus) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BridgeClSetHdrMode(bool enabled) {
|
||||||
|
JNIEnv* env = GetThreadEnv();
|
||||||
|
|
||||||
|
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled);
|
||||||
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
|
// We will crash here
|
||||||
|
(*JVM)->DetachCurrentThread(JVM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BridgeClLogMessage(const char* format, ...) {
|
void BridgeClLogMessage(const char* format, ...) {
|
||||||
va_list va;
|
va_list va;
|
||||||
va_start(va, format);
|
va_start(va, format);
|
||||||
@@ -359,23 +371,27 @@ static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
|
|||||||
.logMessage = BridgeClLogMessage,
|
.logMessage = BridgeClLogMessage,
|
||||||
.rumble = BridgeClRumble,
|
.rumble = BridgeClRumble,
|
||||||
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
|
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
|
||||||
|
.setHdrMode = BridgeClSetHdrMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
|
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
|
||||||
jstring address, jstring appVersion, jstring gfeVersion,
|
jstring address, jstring appVersion, jstring gfeVersion,
|
||||||
|
jstring rtspSessionUrl,
|
||||||
jint width, jint height, jint fps,
|
jint width, jint height, jint fps,
|
||||||
jint bitrate, jint packetSize, jint streamingRemotely,
|
jint bitrate, jint packetSize, jint streamingRemotely,
|
||||||
jint audioConfiguration, jboolean supportsHevc,
|
jint audioConfiguration, jboolean supportsHevc,
|
||||||
jboolean enableHdr,
|
jboolean enableHdr,
|
||||||
jint hevcBitratePercentageMultiplier,
|
jint hevcBitratePercentageMultiplier,
|
||||||
jint clientRefreshRateX100,
|
jint clientRefreshRateX100,
|
||||||
|
jint encryptionFlags,
|
||||||
jbyteArray riAesKey, jbyteArray riAesIv,
|
jbyteArray riAesKey, jbyteArray riAesIv,
|
||||||
jint videoCapabilities) {
|
jint videoCapabilities) {
|
||||||
SERVER_INFORMATION serverInfo = {
|
SERVER_INFORMATION serverInfo = {
|
||||||
.address = (*env)->GetStringUTFChars(env, address, 0),
|
.address = (*env)->GetStringUTFChars(env, address, 0),
|
||||||
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
|
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
|
||||||
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 0) : NULL,
|
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 0) : NULL,
|
||||||
|
.rtspSessionUrl = rtspSessionUrl ? (*env)->GetStringUTFChars(env, rtspSessionUrl, 0) : NULL,
|
||||||
};
|
};
|
||||||
STREAM_CONFIGURATION streamConfig = {
|
STREAM_CONFIGURATION streamConfig = {
|
||||||
.width = width,
|
.width = width,
|
||||||
@@ -388,7 +404,8 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
|||||||
.supportsHevc = supportsHevc,
|
.supportsHevc = supportsHevc,
|
||||||
.enableHdr = enableHdr,
|
.enableHdr = enableHdr,
|
||||||
.hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier,
|
.hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier,
|
||||||
.clientRefreshRateX100 = clientRefreshRateX100
|
.clientRefreshRateX100 = clientRefreshRateX100,
|
||||||
|
.encryptionFlags = encryptionFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
jbyte* riAesKeyBuf = (*env)->GetByteArrayElements(env, riAesKey, NULL);
|
jbyte* riAesKeyBuf = (*env)->GetByteArrayElements(env, riAesKey, NULL);
|
||||||
@@ -414,6 +431,9 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
|||||||
if (gfeVersion != NULL) {
|
if (gfeVersion != NULL) {
|
||||||
(*env)->ReleaseStringUTFChars(env, gfeVersion, serverInfo.serverInfoGfeVersion);
|
(*env)->ReleaseStringUTFChars(env, gfeVersion, serverInfo.serverInfoGfeVersion);
|
||||||
}
|
}
|
||||||
|
if (rtspSessionUrl != NULL) {
|
||||||
|
(*env)->ReleaseStringUTFChars(env, rtspSessionUrl, serverInfo.rtspSessionUrl);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 021fe902d9...d247873ade.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -8,9 +8,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is only used by HP C on VMS, and is included automatically
|
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||||
* after each header file from this directory
|
* after each header file from this directory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The C++ compiler doesn't understand these pragmas, even though it
|
||||||
|
* understands the corresponding command line qualifier.
|
||||||
|
*/
|
||||||
|
#ifndef __cplusplus
|
||||||
/* restore state. Must correspond to the save in __decc_include_prologue.h */
|
/* restore state. Must correspond to the save in __decc_include_prologue.h */
|
||||||
#pragma names restore
|
# pragma names restore
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -8,13 +8,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is only used by HP C on VMS, and is included automatically
|
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||||
* after each header file from this directory
|
* after each header file from this directory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The C++ compiler doesn't understand these pragmas, even though it
|
||||||
|
* understands the corresponding command line qualifier.
|
||||||
|
*/
|
||||||
|
#ifndef __cplusplus
|
||||||
/* save state */
|
/* save state */
|
||||||
#pragma names save
|
# pragma names save
|
||||||
/* have the compiler shorten symbols larger than 31 chars to 23 chars
|
/* have the compiler shorten symbols larger than 31 chars to 23 chars
|
||||||
* followed by a 8 hex char CRC
|
* followed by a 8 hex char CRC
|
||||||
*/
|
*/
|
||||||
#pragma names as_is,shortened
|
# pragma names as_is,shortened
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
#ifndef HEADER_ASN1ERR_H
|
#ifndef HEADER_ASN1ERR_H
|
||||||
# define HEADER_ASN1ERR_H
|
# define HEADER_ASN1ERR_H
|
||||||
|
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
@@ -49,6 +51,7 @@ int ERR_load_ASN1_strings(void);
|
|||||||
# define ASN1_F_ASN1_ITEM_DUP 191
|
# define ASN1_F_ASN1_ITEM_DUP 191
|
||||||
# define ASN1_F_ASN1_ITEM_EMBED_D2I 120
|
# define ASN1_F_ASN1_ITEM_EMBED_D2I 120
|
||||||
# define ASN1_F_ASN1_ITEM_EMBED_NEW 121
|
# define ASN1_F_ASN1_ITEM_EMBED_NEW 121
|
||||||
|
# define ASN1_F_ASN1_ITEM_EX_I2D 144
|
||||||
# define ASN1_F_ASN1_ITEM_FLAGS_I2D 118
|
# define ASN1_F_ASN1_ITEM_FLAGS_I2D 118
|
||||||
# define ASN1_F_ASN1_ITEM_I2D_BIO 192
|
# define ASN1_F_ASN1_ITEM_I2D_BIO 192
|
||||||
# define ASN1_F_ASN1_ITEM_I2D_FP 193
|
# define ASN1_F_ASN1_ITEM_I2D_FP 193
|
||||||
@@ -141,6 +144,7 @@ int ERR_load_ASN1_strings(void);
|
|||||||
# define ASN1_R_ASN1_SIG_PARSE_ERROR 204
|
# define ASN1_R_ASN1_SIG_PARSE_ERROR 204
|
||||||
# define ASN1_R_AUX_ERROR 100
|
# define ASN1_R_AUX_ERROR 100
|
||||||
# define ASN1_R_BAD_OBJECT_HEADER 102
|
# define ASN1_R_BAD_OBJECT_HEADER 102
|
||||||
|
# define ASN1_R_BAD_TEMPLATE 230
|
||||||
# define ASN1_R_BMPSTRING_IS_WRONG_LENGTH 214
|
# define ASN1_R_BMPSTRING_IS_WRONG_LENGTH 214
|
||||||
# define ASN1_R_BN_LIB 105
|
# define ASN1_R_BN_LIB 105
|
||||||
# define ASN1_R_BOOLEAN_IS_WRONG_LENGTH 106
|
# define ASN1_R_BOOLEAN_IS_WRONG_LENGTH 106
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_ASYNCERR_H
|
#ifndef HEADER_ASYNCERR_H
|
||||||
# define HEADER_ASYNCERR_H
|
# define HEADER_ASYNCERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -20,10 +20,6 @@
|
|||||||
# include <openssl/crypto.h>
|
# include <openssl/crypto.h>
|
||||||
# include <openssl/bioerr.h>
|
# include <openssl/bioerr.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_SCTP
|
|
||||||
# include <openssl/e_os2.h>
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -173,6 +169,7 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
# define BIO_FLAGS_MEM_RDONLY 0x200
|
# define BIO_FLAGS_MEM_RDONLY 0x200
|
||||||
# define BIO_FLAGS_NONCLEAR_RST 0x400
|
# define BIO_FLAGS_NONCLEAR_RST 0x400
|
||||||
|
# define BIO_FLAGS_IN_EOF 0x800
|
||||||
|
|
||||||
typedef union bio_addr_st BIO_ADDR;
|
typedef union bio_addr_st BIO_ADDR;
|
||||||
typedef struct bio_addrinfo_st BIO_ADDRINFO;
|
typedef struct bio_addrinfo_st BIO_ADDRINFO;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_BIOERR_H
|
#ifndef HEADER_BIOERR_H
|
||||||
# define HEADER_BIOERR_H
|
# define HEADER_BIOERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
@@ -56,7 +56,7 @@ extern "C" {
|
|||||||
* avoid leaking exponent information through timing,
|
* avoid leaking exponent information through timing,
|
||||||
* BN_mod_exp_mont() will call BN_mod_exp_mont_consttime,
|
* BN_mod_exp_mont() will call BN_mod_exp_mont_consttime,
|
||||||
* BN_div() will call BN_div_no_branch,
|
* BN_div() will call BN_div_no_branch,
|
||||||
* BN_mod_inverse() will call BN_mod_inverse_no_branch.
|
* BN_mod_inverse() will call bn_mod_inverse_no_branch.
|
||||||
*/
|
*/
|
||||||
# define BN_FLG_CONSTTIME 0x04
|
# define BN_FLG_CONSTTIME 0x04
|
||||||
# define BN_FLG_SECURE 0x08
|
# define BN_FLG_SECURE 0x08
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_BNERR_H
|
#ifndef HEADER_BNERR_H
|
||||||
# define HEADER_BNERR_H
|
# define HEADER_BNERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_BUFERR_H
|
#ifndef HEADER_BUFERR_H
|
||||||
# define HEADER_BUFERR_H
|
# define HEADER_BUFERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2008-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -284,8 +284,6 @@ int CMS_unsigned_add1_attr_by_txt(CMS_SignerInfo *si,
|
|||||||
void *CMS_unsigned_get0_data_by_OBJ(CMS_SignerInfo *si, ASN1_OBJECT *oid,
|
void *CMS_unsigned_get0_data_by_OBJ(CMS_SignerInfo *si, ASN1_OBJECT *oid,
|
||||||
int lastpos, int type);
|
int lastpos, int type);
|
||||||
|
|
||||||
# ifdef HEADER_X509V3_H
|
|
||||||
|
|
||||||
int CMS_get1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest **prr);
|
int CMS_get1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest **prr);
|
||||||
CMS_ReceiptRequest *CMS_ReceiptRequest_create0(unsigned char *id, int idlen,
|
CMS_ReceiptRequest *CMS_ReceiptRequest_create0(unsigned char *id, int idlen,
|
||||||
int allorfirst,
|
int allorfirst,
|
||||||
@@ -298,7 +296,6 @@ void CMS_ReceiptRequest_get0_values(CMS_ReceiptRequest *rr,
|
|||||||
int *pallorfirst,
|
int *pallorfirst,
|
||||||
STACK_OF(GENERAL_NAMES) **plist,
|
STACK_OF(GENERAL_NAMES) **plist,
|
||||||
STACK_OF(GENERAL_NAMES) **prto);
|
STACK_OF(GENERAL_NAMES) **prto);
|
||||||
# endif
|
|
||||||
int CMS_RecipientInfo_kari_get0_alg(CMS_RecipientInfo *ri,
|
int CMS_RecipientInfo_kari_get0_alg(CMS_RecipientInfo *ri,
|
||||||
X509_ALGOR **palg,
|
X509_ALGOR **palg,
|
||||||
ASN1_OCTET_STRING **pukm);
|
ASN1_OCTET_STRING **pukm);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_CMSERR_H
|
#ifndef HEADER_CMSERR_H
|
||||||
# define HEADER_CMSERR_H
|
# define HEADER_CMSERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_CMS
|
# ifndef OPENSSL_NO_CMS
|
||||||
@@ -101,6 +105,7 @@ int ERR_load_CMS_strings(void);
|
|||||||
# define CMS_F_CMS_SIGNERINFO_VERIFY_CERT 153
|
# define CMS_F_CMS_SIGNERINFO_VERIFY_CERT 153
|
||||||
# define CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT 154
|
# define CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT 154
|
||||||
# define CMS_F_CMS_SIGN_RECEIPT 163
|
# define CMS_F_CMS_SIGN_RECEIPT 163
|
||||||
|
# define CMS_F_CMS_SI_CHECK_ATTRIBUTES 183
|
||||||
# define CMS_F_CMS_STREAM 155
|
# define CMS_F_CMS_STREAM 155
|
||||||
# define CMS_F_CMS_UNCOMPRESS 156
|
# define CMS_F_CMS_UNCOMPRESS 156
|
||||||
# define CMS_F_CMS_VERIFY 157
|
# define CMS_F_CMS_VERIFY 157
|
||||||
@@ -110,6 +115,7 @@ int ERR_load_CMS_strings(void);
|
|||||||
* CMS reason codes.
|
* CMS reason codes.
|
||||||
*/
|
*/
|
||||||
# define CMS_R_ADD_SIGNER_ERROR 99
|
# define CMS_R_ADD_SIGNER_ERROR 99
|
||||||
|
# define CMS_R_ATTRIBUTE_ERROR 161
|
||||||
# define CMS_R_CERTIFICATE_ALREADY_PRESENT 175
|
# define CMS_R_CERTIFICATE_ALREADY_PRESENT 175
|
||||||
# define CMS_R_CERTIFICATE_HAS_NO_KEYID 160
|
# define CMS_R_CERTIFICATE_HAS_NO_KEYID 160
|
||||||
# define CMS_R_CERTIFICATE_VERIFY_ERROR 100
|
# define CMS_R_CERTIFICATE_VERIFY_ERROR 100
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_COMPERR_H
|
#ifndef HEADER_COMPERR_H
|
||||||
# define HEADER_COMPERR_H
|
# define HEADER_COMPERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_COMP
|
# ifndef OPENSSL_NO_COMP
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_CONFERR_H
|
#ifndef HEADER_CONFERR_H
|
||||||
# define HEADER_CONFERR_H
|
# define HEADER_CONFERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,12 +11,13 @@
|
|||||||
#ifndef HEADER_CRYPTOERR_H
|
#ifndef HEADER_CRYPTOERR_H
|
||||||
# define HEADER_CRYPTOERR_H
|
# define HEADER_CRYPTOERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# include <openssl/symhacks.h>
|
|
||||||
|
|
||||||
int ERR_load_CRYPTO_strings(void);
|
int ERR_load_CRYPTO_strings(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -463,8 +463,6 @@ __owur int CTLOG_STORE_load_file(CTLOG_STORE *store, const char *file);
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads the default CT log list into a |store|.
|
* Loads the default CT log list into a |store|.
|
||||||
* See internal/cryptlib.h for the environment variable and file path that are
|
|
||||||
* consulted to find the default file.
|
|
||||||
* Returns 1 if loading is successful, or 0 otherwise.
|
* Returns 1 if loading is successful, or 0 otherwise.
|
||||||
*/
|
*/
|
||||||
__owur int CTLOG_STORE_load_default_file(CTLOG_STORE *store);
|
__owur int CTLOG_STORE_load_default_file(CTLOG_STORE *store);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_CTERR_H
|
#ifndef HEADER_CTERR_H
|
||||||
# define HEADER_CTERR_H
|
# define HEADER_CTERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_CT
|
# ifndef OPENSSL_NO_CT
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_DHERR_H
|
#ifndef HEADER_DHERR_H
|
||||||
# define HEADER_DHERR_H
|
# define HEADER_DHERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_DH
|
# ifndef OPENSSL_NO_DH
|
||||||
|
|||||||
@@ -162,6 +162,12 @@ DH *DSA_dup_DH(const DSA *r);
|
|||||||
# define EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, nbits) \
|
# define EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, nbits) \
|
||||||
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||||
EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, nbits, NULL)
|
EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, nbits, NULL)
|
||||||
|
# define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \
|
||||||
|
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||||
|
EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, qbits, NULL)
|
||||||
|
# define EVP_PKEY_CTX_set_dsa_paramgen_md(ctx, md) \
|
||||||
|
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||||
|
EVP_PKEY_CTRL_DSA_PARAMGEN_MD, 0, (void *)(md))
|
||||||
|
|
||||||
# define EVP_PKEY_CTRL_DSA_PARAMGEN_BITS (EVP_PKEY_ALG_CTRL + 1)
|
# define EVP_PKEY_CTRL_DSA_PARAMGEN_BITS (EVP_PKEY_ALG_CTRL + 1)
|
||||||
# define EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS (EVP_PKEY_ALG_CTRL + 2)
|
# define EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS (EVP_PKEY_ALG_CTRL + 2)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_DSAERR_H
|
#ifndef HEADER_DSAERR_H
|
||||||
# define HEADER_DSAERR_H
|
# define HEADER_DSAERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_DSA
|
# ifndef OPENSSL_NO_DSA
|
||||||
@@ -57,6 +61,7 @@ int ERR_load_DSA_strings(void);
|
|||||||
# define DSA_R_INVALID_DIGEST_TYPE 106
|
# define DSA_R_INVALID_DIGEST_TYPE 106
|
||||||
# define DSA_R_INVALID_PARAMETERS 112
|
# define DSA_R_INVALID_PARAMETERS 112
|
||||||
# define DSA_R_MISSING_PARAMETERS 101
|
# define DSA_R_MISSING_PARAMETERS 101
|
||||||
|
# define DSA_R_MISSING_PRIVATE_KEY 111
|
||||||
# define DSA_R_MODULUS_TOO_LARGE 103
|
# define DSA_R_MODULUS_TOO_LARGE 103
|
||||||
# define DSA_R_NO_PARAMETERS_SET 107
|
# define DSA_R_NO_PARAMETERS_SET 107
|
||||||
# define DSA_R_PARAMETER_ENCODING_ERROR 105
|
# define DSA_R_PARAMETER_ENCODING_ERROR 105
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ extern "C" {
|
|||||||
|
|
||||||
# define DTLS1_AL_HEADER_LENGTH 2
|
# define DTLS1_AL_HEADER_LENGTH 2
|
||||||
|
|
||||||
/* Timeout multipliers (timeout slice is defined in apps/timeouts.h */
|
/* Timeout multipliers */
|
||||||
# define DTLS1_TMO_READ_COUNT 2
|
# define DTLS1_TMO_READ_COUNT 2
|
||||||
# define DTLS1_TMO_WRITE_COUNT 2
|
# define DTLS1_TMO_WRITE_COUNT 2
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -241,7 +241,7 @@ typedef UINT64 uint64_t;
|
|||||||
defined(__osf__) || defined(__sgi) || defined(__hpux) || \
|
defined(__osf__) || defined(__sgi) || defined(__hpux) || \
|
||||||
defined(OPENSSL_SYS_VMS) || defined (__OpenBSD__)
|
defined(OPENSSL_SYS_VMS) || defined (__OpenBSD__)
|
||||||
# include <inttypes.h>
|
# include <inttypes.h>
|
||||||
# elif defined(_MSC_VER) && _MSC_VER<=1500
|
# elif defined(_MSC_VER) && _MSC_VER<1600
|
||||||
/*
|
/*
|
||||||
* minimally required typdefs for systems not supporting inttypes.h or
|
* minimally required typdefs for systems not supporting inttypes.h or
|
||||||
* stdint.h: currently just older VC++
|
* stdint.h: currently just older VC++
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
@@ -142,7 +142,7 @@ const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group);
|
|||||||
*/
|
*/
|
||||||
int EC_METHOD_get_field_type(const EC_METHOD *meth);
|
int EC_METHOD_get_field_type(const EC_METHOD *meth);
|
||||||
|
|
||||||
/** Sets the generator and it's order/cofactor of a EC_GROUP object.
|
/** Sets the generator and its order/cofactor of a EC_GROUP object.
|
||||||
* \param group EC_GROUP object
|
* \param group EC_GROUP object
|
||||||
* \param generator EC_POINT object with the generator.
|
* \param generator EC_POINT object with the generator.
|
||||||
* \param order the order of the group generated by the generator.
|
* \param order the order of the group generated by the generator.
|
||||||
@@ -829,6 +829,8 @@ void EC_KEY_set_flags(EC_KEY *key, int flags);
|
|||||||
|
|
||||||
void EC_KEY_clear_flags(EC_KEY *key, int flags);
|
void EC_KEY_clear_flags(EC_KEY *key, int flags);
|
||||||
|
|
||||||
|
int EC_KEY_decoded_from_explicit_params(const EC_KEY *key);
|
||||||
|
|
||||||
/** Creates a new EC_KEY object using a named curve as underlying
|
/** Creates a new EC_KEY object using a named curve as underlying
|
||||||
* EC_GROUP object.
|
* EC_GROUP object.
|
||||||
* \param nid NID of the named curve.
|
* \param nid NID of the named curve.
|
||||||
@@ -1138,7 +1140,8 @@ void ECDSA_SIG_free(ECDSA_SIG *sig);
|
|||||||
* (*pp += length of the DER encoded signature)).
|
* (*pp += length of the DER encoded signature)).
|
||||||
* \param sig pointer to the ECDSA_SIG object
|
* \param sig pointer to the ECDSA_SIG object
|
||||||
* \param pp pointer to a unsigned char pointer for the output or NULL
|
* \param pp pointer to a unsigned char pointer for the output or NULL
|
||||||
* \return the length of the DER encoded ECDSA_SIG object or 0
|
* \return the length of the DER encoded ECDSA_SIG object or a negative value
|
||||||
|
* on error
|
||||||
*/
|
*/
|
||||||
int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp);
|
int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_ECERR_H
|
#ifndef HEADER_ECERR_H
|
||||||
# define HEADER_ECERR_H
|
# define HEADER_ECERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_EC
|
# ifndef OPENSSL_NO_EC
|
||||||
@@ -239,6 +243,7 @@ int ERR_load_EC_strings(void);
|
|||||||
# define EC_R_LADDER_POST_FAILURE 136
|
# define EC_R_LADDER_POST_FAILURE 136
|
||||||
# define EC_R_LADDER_PRE_FAILURE 153
|
# define EC_R_LADDER_PRE_FAILURE 153
|
||||||
# define EC_R_LADDER_STEP_FAILURE 162
|
# define EC_R_LADDER_STEP_FAILURE 162
|
||||||
|
# define EC_R_MISSING_OID 167
|
||||||
# define EC_R_MISSING_PARAMETERS 124
|
# define EC_R_MISSING_PARAMETERS 124
|
||||||
# define EC_R_MISSING_PRIVATE_KEY 125
|
# define EC_R_MISSING_PRIVATE_KEY 125
|
||||||
# define EC_R_NEED_NEW_SETUP_VALUES 157
|
# define EC_R_NEED_NEW_SETUP_VALUES 157
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_ENGINEERR_H
|
#ifndef HEADER_ENGINEERR_H
|
||||||
# define HEADER_ENGINEERR_H
|
# define HEADER_ENGINEERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_ENGINE
|
# ifndef OPENSSL_NO_ENGINE
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -37,6 +37,7 @@ extern "C" {
|
|||||||
# define ERR_TXT_STRING 0x02
|
# define ERR_TXT_STRING 0x02
|
||||||
|
|
||||||
# define ERR_FLAG_MARK 0x01
|
# define ERR_FLAG_MARK 0x01
|
||||||
|
# define ERR_FLAG_CLEAR 0x02
|
||||||
|
|
||||||
# define ERR_NUM_ERRORS 16
|
# define ERR_NUM_ERRORS 16
|
||||||
typedef struct err_state_st {
|
typedef struct err_state_st {
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ int (*EVP_MD_meth_get_ctrl(const EVP_MD *md))(EVP_MD_CTX *ctx, int cmd,
|
|||||||
* if the following flag is set.
|
* if the following flag is set.
|
||||||
*/
|
*/
|
||||||
# define EVP_MD_CTX_FLAG_FINALISE 0x0200
|
# define EVP_MD_CTX_FLAG_FINALISE 0x0200
|
||||||
/* NOTE: 0x0400 is reserved for internal usage in evp_int.h */
|
/* NOTE: 0x0400 is reserved for internal usage */
|
||||||
|
|
||||||
EVP_CIPHER *EVP_CIPHER_meth_new(int cipher_type, int block_size, int key_len);
|
EVP_CIPHER *EVP_CIPHER_meth_new(int cipher_type, int block_size, int key_len);
|
||||||
EVP_CIPHER *EVP_CIPHER_meth_dup(const EVP_CIPHER *cipher);
|
EVP_CIPHER *EVP_CIPHER_meth_dup(const EVP_CIPHER *cipher);
|
||||||
@@ -260,6 +260,8 @@ int (*EVP_CIPHER_meth_get_ctrl(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *,
|
|||||||
# define EVP_CIPH_RAND_KEY 0x200
|
# define EVP_CIPH_RAND_KEY 0x200
|
||||||
/* cipher has its own additional copying logic */
|
/* cipher has its own additional copying logic */
|
||||||
# define EVP_CIPH_CUSTOM_COPY 0x400
|
# define EVP_CIPH_CUSTOM_COPY 0x400
|
||||||
|
/* Don't use standard iv length function */
|
||||||
|
# define EVP_CIPH_CUSTOM_IV_LENGTH 0x800
|
||||||
/* Allow use default ASN1 get/set iv */
|
/* Allow use default ASN1 get/set iv */
|
||||||
# define EVP_CIPH_FLAG_DEFAULT_ASN1 0x1000
|
# define EVP_CIPH_FLAG_DEFAULT_ASN1 0x1000
|
||||||
/* Buffer length in bits not bytes: CFB1 mode only */
|
/* Buffer length in bits not bytes: CFB1 mode only */
|
||||||
@@ -349,6 +351,8 @@ int (*EVP_CIPHER_meth_get_ctrl(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *,
|
|||||||
/* Set the input buffer lengths to use for a pipelined operation */
|
/* Set the input buffer lengths to use for a pipelined operation */
|
||||||
# define EVP_CTRL_SET_PIPELINE_INPUT_LENS 0x24
|
# define EVP_CTRL_SET_PIPELINE_INPUT_LENS 0x24
|
||||||
|
|
||||||
|
# define EVP_CTRL_GET_IVLEN 0x25
|
||||||
|
|
||||||
/* Padding modes */
|
/* Padding modes */
|
||||||
#define EVP_PADDING_PKCS7 1
|
#define EVP_PADDING_PKCS7 1
|
||||||
#define EVP_PADDING_ISO7816_4 2
|
#define EVP_PADDING_ISO7816_4 2
|
||||||
@@ -995,6 +999,7 @@ int EVP_PKEY_set_type_str(EVP_PKEY *pkey, const char *str, int len);
|
|||||||
int EVP_PKEY_set_alias_type(EVP_PKEY *pkey, int type);
|
int EVP_PKEY_set_alias_type(EVP_PKEY *pkey, int type);
|
||||||
# ifndef OPENSSL_NO_ENGINE
|
# ifndef OPENSSL_NO_ENGINE
|
||||||
int EVP_PKEY_set1_engine(EVP_PKEY *pkey, ENGINE *e);
|
int EVP_PKEY_set1_engine(EVP_PKEY *pkey, ENGINE *e);
|
||||||
|
ENGINE *EVP_PKEY_get0_engine(const EVP_PKEY *pkey);
|
||||||
# endif
|
# endif
|
||||||
int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key);
|
int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key);
|
||||||
void *EVP_PKEY_get0(const EVP_PKEY *pkey);
|
void *EVP_PKEY_get0(const EVP_PKEY *pkey);
|
||||||
@@ -1507,6 +1512,20 @@ void EVP_PKEY_meth_set_ctrl(EVP_PKEY_METHOD *pmeth,
|
|||||||
const char *type,
|
const char *type,
|
||||||
const char *value));
|
const char *value));
|
||||||
|
|
||||||
|
void EVP_PKEY_meth_set_digestsign(EVP_PKEY_METHOD *pmeth,
|
||||||
|
int (*digestsign) (EVP_MD_CTX *ctx,
|
||||||
|
unsigned char *sig,
|
||||||
|
size_t *siglen,
|
||||||
|
const unsigned char *tbs,
|
||||||
|
size_t tbslen));
|
||||||
|
|
||||||
|
void EVP_PKEY_meth_set_digestverify(EVP_PKEY_METHOD *pmeth,
|
||||||
|
int (*digestverify) (EVP_MD_CTX *ctx,
|
||||||
|
const unsigned char *sig,
|
||||||
|
size_t siglen,
|
||||||
|
const unsigned char *tbs,
|
||||||
|
size_t tbslen));
|
||||||
|
|
||||||
void EVP_PKEY_meth_set_check(EVP_PKEY_METHOD *pmeth,
|
void EVP_PKEY_meth_set_check(EVP_PKEY_METHOD *pmeth,
|
||||||
int (*check) (EVP_PKEY *pkey));
|
int (*check) (EVP_PKEY *pkey));
|
||||||
|
|
||||||
@@ -1612,6 +1631,20 @@ void EVP_PKEY_meth_get_ctrl(const EVP_PKEY_METHOD *pmeth,
|
|||||||
const char *type,
|
const char *type,
|
||||||
const char *value));
|
const char *value));
|
||||||
|
|
||||||
|
void EVP_PKEY_meth_get_digestsign(EVP_PKEY_METHOD *pmeth,
|
||||||
|
int (**digestsign) (EVP_MD_CTX *ctx,
|
||||||
|
unsigned char *sig,
|
||||||
|
size_t *siglen,
|
||||||
|
const unsigned char *tbs,
|
||||||
|
size_t tbslen));
|
||||||
|
|
||||||
|
void EVP_PKEY_meth_get_digestverify(EVP_PKEY_METHOD *pmeth,
|
||||||
|
int (**digestverify) (EVP_MD_CTX *ctx,
|
||||||
|
const unsigned char *sig,
|
||||||
|
size_t siglen,
|
||||||
|
const unsigned char *tbs,
|
||||||
|
size_t tbslen));
|
||||||
|
|
||||||
void EVP_PKEY_meth_get_check(const EVP_PKEY_METHOD *pmeth,
|
void EVP_PKEY_meth_get_check(const EVP_PKEY_METHOD *pmeth,
|
||||||
int (**pcheck) (EVP_PKEY *pkey));
|
int (**pcheck) (EVP_PKEY *pkey));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
#ifndef HEADER_EVPERR_H
|
#ifndef HEADER_EVPERR_H
|
||||||
# define HEADER_EVPERR_H
|
# define HEADER_EVPERR_H
|
||||||
|
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
@@ -20,11 +22,14 @@ int ERR_load_EVP_strings(void);
|
|||||||
* EVP function codes.
|
* EVP function codes.
|
||||||
*/
|
*/
|
||||||
# define EVP_F_AESNI_INIT_KEY 165
|
# define EVP_F_AESNI_INIT_KEY 165
|
||||||
|
# define EVP_F_AESNI_XTS_INIT_KEY 207
|
||||||
# define EVP_F_AES_GCM_CTRL 196
|
# define EVP_F_AES_GCM_CTRL 196
|
||||||
# define EVP_F_AES_INIT_KEY 133
|
# define EVP_F_AES_INIT_KEY 133
|
||||||
# define EVP_F_AES_OCB_CIPHER 169
|
# define EVP_F_AES_OCB_CIPHER 169
|
||||||
# define EVP_F_AES_T4_INIT_KEY 178
|
# define EVP_F_AES_T4_INIT_KEY 178
|
||||||
|
# define EVP_F_AES_T4_XTS_INIT_KEY 208
|
||||||
# define EVP_F_AES_WRAP_CIPHER 170
|
# define EVP_F_AES_WRAP_CIPHER 170
|
||||||
|
# define EVP_F_AES_XTS_INIT_KEY 209
|
||||||
# define EVP_F_ALG_MODULE_INIT 177
|
# define EVP_F_ALG_MODULE_INIT 177
|
||||||
# define EVP_F_ARIA_CCM_INIT_KEY 175
|
# define EVP_F_ARIA_CCM_INIT_KEY 175
|
||||||
# define EVP_F_ARIA_GCM_CTRL 197
|
# define EVP_F_ARIA_GCM_CTRL 197
|
||||||
@@ -115,6 +120,7 @@ int ERR_load_EVP_strings(void);
|
|||||||
# define EVP_F_PKEY_SET_TYPE 158
|
# define EVP_F_PKEY_SET_TYPE 158
|
||||||
# define EVP_F_RC2_MAGIC_TO_METH 109
|
# define EVP_F_RC2_MAGIC_TO_METH 109
|
||||||
# define EVP_F_RC5_CTRL 125
|
# define EVP_F_RC5_CTRL 125
|
||||||
|
# define EVP_F_R_32_12_16_INIT_KEY 242
|
||||||
# define EVP_F_S390X_AES_GCM_CTRL 201
|
# define EVP_F_S390X_AES_GCM_CTRL 201
|
||||||
# define EVP_F_UPDATE 173
|
# define EVP_F_UPDATE 173
|
||||||
|
|
||||||
@@ -124,6 +130,7 @@ int ERR_load_EVP_strings(void);
|
|||||||
# define EVP_R_AES_KEY_SETUP_FAILED 143
|
# define EVP_R_AES_KEY_SETUP_FAILED 143
|
||||||
# define EVP_R_ARIA_KEY_SETUP_FAILED 176
|
# define EVP_R_ARIA_KEY_SETUP_FAILED 176
|
||||||
# define EVP_R_BAD_DECRYPT 100
|
# define EVP_R_BAD_DECRYPT 100
|
||||||
|
# define EVP_R_BAD_KEY_LENGTH 195
|
||||||
# define EVP_R_BUFFER_TOO_SMALL 155
|
# define EVP_R_BUFFER_TOO_SMALL 155
|
||||||
# define EVP_R_CAMELLIA_KEY_SETUP_FAILED 157
|
# define EVP_R_CAMELLIA_KEY_SETUP_FAILED 157
|
||||||
# define EVP_R_CIPHER_PARAMETER_ERROR 122
|
# define EVP_R_CIPHER_PARAMETER_ERROR 122
|
||||||
@@ -151,6 +158,7 @@ int ERR_load_EVP_strings(void);
|
|||||||
# define EVP_R_INPUT_NOT_INITIALIZED 111
|
# define EVP_R_INPUT_NOT_INITIALIZED 111
|
||||||
# define EVP_R_INVALID_DIGEST 152
|
# define EVP_R_INVALID_DIGEST 152
|
||||||
# define EVP_R_INVALID_FIPS_MODE 168
|
# define EVP_R_INVALID_FIPS_MODE 168
|
||||||
|
# define EVP_R_INVALID_IV_LENGTH 194
|
||||||
# define EVP_R_INVALID_KEY 163
|
# define EVP_R_INVALID_KEY 163
|
||||||
# define EVP_R_INVALID_KEY_LENGTH 130
|
# define EVP_R_INVALID_KEY_LENGTH 130
|
||||||
# define EVP_R_INVALID_OPERATION 148
|
# define EVP_R_INVALID_OPERATION 148
|
||||||
@@ -169,6 +177,7 @@ int ERR_load_EVP_strings(void);
|
|||||||
# define EVP_R_ONLY_ONESHOT_SUPPORTED 177
|
# define EVP_R_ONLY_ONESHOT_SUPPORTED 177
|
||||||
# define EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 150
|
# define EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 150
|
||||||
# define EVP_R_OPERATON_NOT_INITIALIZED 151
|
# define EVP_R_OPERATON_NOT_INITIALIZED 151
|
||||||
|
# define EVP_R_OUTPUT_WOULD_OVERFLOW 184
|
||||||
# define EVP_R_PARTIALLY_OVERLAPPING 162
|
# define EVP_R_PARTIALLY_OVERLAPPING 162
|
||||||
# define EVP_R_PBKDF2_ERROR 181
|
# define EVP_R_PBKDF2_ERROR 181
|
||||||
# define EVP_R_PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED 179
|
# define EVP_R_PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED 179
|
||||||
@@ -190,5 +199,6 @@ int ERR_load_EVP_strings(void);
|
|||||||
# define EVP_R_UNSUPPORTED_SALT_TYPE 126
|
# define EVP_R_UNSUPPORTED_SALT_TYPE 126
|
||||||
# define EVP_R_WRAP_MODE_NOT_ALLOWED 170
|
# define EVP_R_WRAP_MODE_NOT_ALLOWED 170
|
||||||
# define EVP_R_WRONG_FINAL_BLOCK_LENGTH 109
|
# define EVP_R_WRONG_FINAL_BLOCK_LENGTH 109
|
||||||
|
# define EVP_R_XTS_DUPLICATED_KEYS 183
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_KDFERR_H
|
#ifndef HEADER_KDFERR_H
|
||||||
# define HEADER_KDFERR_H
|
# define HEADER_KDFERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -120,9 +120,8 @@ void OPENSSL_LH_node_usage_stats_bio(const OPENSSL_LHASH *lh, BIO *out);
|
|||||||
|
|
||||||
# define DEFINE_LHASH_OF(type) \
|
# define DEFINE_LHASH_OF(type) \
|
||||||
LHASH_OF(type) { union lh_##type##_dummy { void* d1; unsigned long d2; int d3; } dummy; }; \
|
LHASH_OF(type) { union lh_##type##_dummy { void* d1; unsigned long d2; int d3; } dummy; }; \
|
||||||
static ossl_inline LHASH_OF(type) * \
|
static ossl_unused ossl_inline LHASH_OF(type) *lh_##type##_new(unsigned long (*hfn)(const type *), \
|
||||||
lh_##type##_new(unsigned long (*hfn)(const type *), \
|
int (*cfn)(const type *, const type *)) \
|
||||||
int (*cfn)(const type *, const type *)) \
|
|
||||||
{ \
|
{ \
|
||||||
return (LHASH_OF(type) *) \
|
return (LHASH_OF(type) *) \
|
||||||
OPENSSL_LH_new((OPENSSL_LH_HASHFUNC)hfn, (OPENSSL_LH_COMPFUNC)cfn); \
|
OPENSSL_LH_new((OPENSSL_LH_HASHFUNC)hfn, (OPENSSL_LH_COMPFUNC)cfn); \
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* WARNING: do not edit!
|
* WARNING: do not edit!
|
||||||
* Generated by crypto/objects/objects.pl
|
* Generated by crypto/objects/objects.pl
|
||||||
*
|
*
|
||||||
* Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
* in the file LICENSE in the source distribution or at
|
* in the file LICENSE in the source distribution or at
|
||||||
@@ -1290,12 +1290,12 @@
|
|||||||
#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L
|
#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L
|
||||||
|
|
||||||
#define SN_ms_smartcard_login "msSmartcardLogin"
|
#define SN_ms_smartcard_login "msSmartcardLogin"
|
||||||
#define LN_ms_smartcard_login "Microsoft Smartcardlogin"
|
#define LN_ms_smartcard_login "Microsoft Smartcard Login"
|
||||||
#define NID_ms_smartcard_login 648
|
#define NID_ms_smartcard_login 648
|
||||||
#define OBJ_ms_smartcard_login 1L,3L,6L,1L,4L,1L,311L,20L,2L,2L
|
#define OBJ_ms_smartcard_login 1L,3L,6L,1L,4L,1L,311L,20L,2L,2L
|
||||||
|
|
||||||
#define SN_ms_upn "msUPN"
|
#define SN_ms_upn "msUPN"
|
||||||
#define LN_ms_upn "Microsoft Universal Principal Name"
|
#define LN_ms_upn "Microsoft User Principal Name"
|
||||||
#define NID_ms_upn 649
|
#define NID_ms_upn 649
|
||||||
#define OBJ_ms_upn 1L,3L,6L,1L,4L,1L,311L,20L,2L,3L
|
#define OBJ_ms_upn 1L,3L,6L,1L,4L,1L,311L,20L,2L,3L
|
||||||
|
|
||||||
@@ -4280,7 +4280,7 @@
|
|||||||
|
|
||||||
#define SN_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 "id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15"
|
#define SN_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 "id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15"
|
||||||
#define NID_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 1183
|
#define NID_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 1183
|
||||||
#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_magma,1L
|
#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik,1L
|
||||||
|
|
||||||
#define SN_id_tc26_constants "id-tc26-constants"
|
#define SN_id_tc26_constants "id-tc26-constants"
|
||||||
#define NID_id_tc26_constants 994
|
#define NID_id_tc26_constants 994
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_OBJERR_H
|
#ifndef HEADER_OBJERR_H
|
||||||
# define HEADER_OBJERR_H
|
# define HEADER_OBJERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -123,7 +123,7 @@ typedef struct ocsp_service_locator_st OCSP_SERVICELOC;
|
|||||||
(char *(*)())d2i_OCSP_REQUEST,PEM_STRING_OCSP_REQUEST, \
|
(char *(*)())d2i_OCSP_REQUEST,PEM_STRING_OCSP_REQUEST, \
|
||||||
bp,(char **)(x),cb,NULL)
|
bp,(char **)(x),cb,NULL)
|
||||||
|
|
||||||
# define PEM_read_bio_OCSP_RESPONSE(bp,x,cb)(OCSP_RESPONSE *)PEM_ASN1_read_bio(\
|
# define PEM_read_bio_OCSP_RESPONSE(bp,x,cb) (OCSP_RESPONSE *)PEM_ASN1_read_bio(\
|
||||||
(char *(*)())d2i_OCSP_RESPONSE,PEM_STRING_OCSP_RESPONSE, \
|
(char *(*)())d2i_OCSP_RESPONSE,PEM_STRING_OCSP_RESPONSE, \
|
||||||
bp,(char **)(x),cb,NULL)
|
bp,(char **)(x),cb,NULL)
|
||||||
|
|
||||||
@@ -229,8 +229,8 @@ int OCSP_request_verify(OCSP_REQUEST *req, STACK_OF(X509) *certs,
|
|||||||
int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath,
|
int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath,
|
||||||
int *pssl);
|
int *pssl);
|
||||||
|
|
||||||
int OCSP_id_issuer_cmp(OCSP_CERTID *a, OCSP_CERTID *b);
|
int OCSP_id_issuer_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);
|
||||||
int OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b);
|
int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);
|
||||||
|
|
||||||
int OCSP_request_onereq_count(OCSP_REQUEST *req);
|
int OCSP_request_onereq_count(OCSP_REQUEST *req);
|
||||||
OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i);
|
OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_OCSPERR_H
|
#ifndef HEADER_OCSPERR_H
|
||||||
# define HEADER_OCSPERR_H
|
# define HEADER_OCSPERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# include <openssl/opensslconf.h>
|
# include <openssl/opensslconf.h>
|
||||||
|
|
||||||
# ifndef OPENSSL_NO_OCSP
|
# ifndef OPENSSL_NO_OCSP
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* WARNING: do not edit!
|
* WARNING: do not edit!
|
||||||
* Generated by Makefile from include/openssl/opensslconf.h.in
|
* Generated by Makefile from include/openssl/opensslconf.h.in
|
||||||
*
|
*
|
||||||
* Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -24,9 +24,6 @@ extern "C" {
|
|||||||
* OpenSSL was configured with the following options:
|
* OpenSSL was configured with the following options:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef OPENSSL_NO_COMP
|
|
||||||
# define OPENSSL_NO_COMP
|
|
||||||
#endif
|
|
||||||
#ifndef OPENSSL_NO_MD2
|
#ifndef OPENSSL_NO_MD2
|
||||||
# define OPENSSL_NO_MD2
|
# define OPENSSL_NO_MD2
|
||||||
#endif
|
#endif
|
||||||
@@ -48,9 +45,6 @@ extern "C" {
|
|||||||
#ifndef OPENSSL_NO_ASAN
|
#ifndef OPENSSL_NO_ASAN
|
||||||
# define OPENSSL_NO_ASAN
|
# define OPENSSL_NO_ASAN
|
||||||
#endif
|
#endif
|
||||||
#ifndef OPENSSL_NO_ASM
|
|
||||||
# define OPENSSL_NO_ASM
|
|
||||||
#endif
|
|
||||||
#ifndef OPENSSL_NO_CAPIENG
|
#ifndef OPENSSL_NO_CAPIENG
|
||||||
# define OPENSSL_NO_CAPIENG
|
# define OPENSSL_NO_CAPIENG
|
||||||
#endif
|
#endif
|
||||||
@@ -120,9 +114,6 @@ extern "C" {
|
|||||||
#ifndef OPENSSL_NO_DYNAMIC_ENGINE
|
#ifndef OPENSSL_NO_DYNAMIC_ENGINE
|
||||||
# define OPENSSL_NO_DYNAMIC_ENGINE
|
# define OPENSSL_NO_DYNAMIC_ENGINE
|
||||||
#endif
|
#endif
|
||||||
#ifndef OPENSSL_NO_AFALGENG
|
|
||||||
# define OPENSSL_NO_AFALGENG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -144,6 +135,11 @@ extern "C" {
|
|||||||
# undef DECLARE_DEPRECATED
|
# undef DECLARE_DEPRECATED
|
||||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||||
# endif
|
# endif
|
||||||
|
# elif defined(__SUNPRO_C)
|
||||||
|
# if (__SUNPRO_C >= 0x5130)
|
||||||
|
# undef DECLARE_DEPRECATED
|
||||||
|
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||||
|
# endif
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* {- join("\n * ", @autowarntext) -}
|
* {- join("\n * ", @autowarntext) -}
|
||||||
*
|
*
|
||||||
* Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -77,6 +77,11 @@ extern "C" {
|
|||||||
# undef DECLARE_DEPRECATED
|
# undef DECLARE_DEPRECATED
|
||||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||||
# endif
|
# endif
|
||||||
|
# elif defined(__SUNPRO_C)
|
||||||
|
# if (__SUNPRO_C >= 0x5130)
|
||||||
|
# undef DECLARE_DEPRECATED
|
||||||
|
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||||
|
# endif
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1999-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1999-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -39,8 +39,8 @@ extern "C" {
|
|||||||
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
||||||
* major minor fix final patch/beta)
|
* major minor fix final patch/beta)
|
||||||
*/
|
*/
|
||||||
# define OPENSSL_VERSION_NUMBER 0x1010102fL
|
# define OPENSSL_VERSION_NUMBER 0x101010bfL
|
||||||
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1b 26 Feb 2019"
|
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1k 25 Mar 2021"
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* The macros below are to be used for shared library (.so, .dll, ...)
|
* The macros below are to be used for shared library (.so, .dll, ...)
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ typedef struct dsa_method DSA_METHOD;
|
|||||||
|
|
||||||
typedef struct rsa_st RSA;
|
typedef struct rsa_st RSA;
|
||||||
typedef struct rsa_meth_st RSA_METHOD;
|
typedef struct rsa_meth_st RSA_METHOD;
|
||||||
|
typedef struct rsa_pss_params_st RSA_PSS_PARAMS;
|
||||||
|
|
||||||
typedef struct ec_key_st EC_KEY;
|
typedef struct ec_key_st EC_KEY;
|
||||||
typedef struct ec_key_method_st EC_KEY_METHOD;
|
typedef struct ec_key_method_st EC_KEY_METHOD;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_PEMERR_H
|
#ifndef HEADER_PEMERR_H
|
||||||
# define HEADER_PEMERR_H
|
# define HEADER_PEMERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
@@ -57,6 +61,7 @@ int ERR_load_PEM_strings(void);
|
|||||||
# define PEM_F_PEM_SIGNFINAL 112
|
# define PEM_F_PEM_SIGNFINAL 112
|
||||||
# define PEM_F_PEM_WRITE 113
|
# define PEM_F_PEM_WRITE 113
|
||||||
# define PEM_F_PEM_WRITE_BIO 114
|
# define PEM_F_PEM_WRITE_BIO 114
|
||||||
|
# define PEM_F_PEM_WRITE_BIO_PRIVATEKEY_TRADITIONAL 147
|
||||||
# define PEM_F_PEM_WRITE_PRIVATEKEY 139
|
# define PEM_F_PEM_WRITE_PRIVATEKEY 139
|
||||||
# define PEM_F_PEM_X509_INFO_READ 115
|
# define PEM_F_PEM_X509_INFO_READ 115
|
||||||
# define PEM_F_PEM_X509_INFO_READ_BIO 116
|
# define PEM_F_PEM_X509_INFO_READ_BIO 116
|
||||||
@@ -95,5 +100,6 @@ int ERR_load_PEM_strings(void);
|
|||||||
# define PEM_R_UNSUPPORTED_CIPHER 113
|
# define PEM_R_UNSUPPORTED_CIPHER 113
|
||||||
# define PEM_R_UNSUPPORTED_ENCRYPTION 114
|
# define PEM_R_UNSUPPORTED_ENCRYPTION 114
|
||||||
# define PEM_R_UNSUPPORTED_KEY_COMPONENTS 126
|
# define PEM_R_UNSUPPORTED_KEY_COMPONENTS 126
|
||||||
|
# define PEM_R_UNSUPPORTED_PUBLIC_KEY_TYPE 110
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_PKCS12ERR_H
|
#ifndef HEADER_PKCS12ERR_H
|
||||||
# define HEADER_PKCS12ERR_H
|
# define HEADER_PKCS12ERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_PKCS7ERR_H
|
#ifndef HEADER_PKCS7ERR_H
|
||||||
# define HEADER_PKCS7ERR_H
|
# define HEADER_PKCS7ERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
#ifndef HEADER_RANDERR_H
|
#ifndef HEADER_RANDERR_H
|
||||||
# define HEADER_RANDERR_H
|
# define HEADER_RANDERR_H
|
||||||
|
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
@@ -19,6 +21,7 @@ int ERR_load_RAND_strings(void);
|
|||||||
/*
|
/*
|
||||||
* RAND function codes.
|
* RAND function codes.
|
||||||
*/
|
*/
|
||||||
|
# define RAND_F_DATA_COLLECT_METHOD 127
|
||||||
# define RAND_F_DRBG_BYTES 101
|
# define RAND_F_DRBG_BYTES 101
|
||||||
# define RAND_F_DRBG_GET_ENTROPY 105
|
# define RAND_F_DRBG_GET_ENTROPY 105
|
||||||
# define RAND_F_DRBG_SETUP 117
|
# define RAND_F_DRBG_SETUP 117
|
||||||
@@ -42,7 +45,9 @@ int ERR_load_RAND_strings(void);
|
|||||||
# define RAND_F_RAND_POOL_ADD_END 114
|
# define RAND_F_RAND_POOL_ADD_END 114
|
||||||
# define RAND_F_RAND_POOL_ATTACH 124
|
# define RAND_F_RAND_POOL_ATTACH 124
|
||||||
# define RAND_F_RAND_POOL_BYTES_NEEDED 115
|
# define RAND_F_RAND_POOL_BYTES_NEEDED 115
|
||||||
|
# define RAND_F_RAND_POOL_GROW 125
|
||||||
# define RAND_F_RAND_POOL_NEW 116
|
# define RAND_F_RAND_POOL_NEW 116
|
||||||
|
# define RAND_F_RAND_PSEUDO_BYTES 126
|
||||||
# define RAND_F_RAND_WRITE_FILE 112
|
# define RAND_F_RAND_WRITE_FILE 112
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ const BIGNUM *RSA_get0_q(const RSA *d);
|
|||||||
const BIGNUM *RSA_get0_dmp1(const RSA *r);
|
const BIGNUM *RSA_get0_dmp1(const RSA *r);
|
||||||
const BIGNUM *RSA_get0_dmq1(const RSA *r);
|
const BIGNUM *RSA_get0_dmq1(const RSA *r);
|
||||||
const BIGNUM *RSA_get0_iqmp(const RSA *r);
|
const BIGNUM *RSA_get0_iqmp(const RSA *r);
|
||||||
|
const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *r);
|
||||||
void RSA_clear_flags(RSA *r, int flags);
|
void RSA_clear_flags(RSA *r, int flags);
|
||||||
int RSA_test_flags(const RSA *r, int flags);
|
int RSA_test_flags(const RSA *r, int flags);
|
||||||
void RSA_set_flags(RSA *r, int flags);
|
void RSA_set_flags(RSA *r, int flags);
|
||||||
@@ -279,14 +280,14 @@ int RSA_pkey_ctx_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2);
|
|||||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPublicKey)
|
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPublicKey)
|
||||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPrivateKey)
|
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPrivateKey)
|
||||||
|
|
||||||
typedef struct rsa_pss_params_st {
|
struct rsa_pss_params_st {
|
||||||
X509_ALGOR *hashAlgorithm;
|
X509_ALGOR *hashAlgorithm;
|
||||||
X509_ALGOR *maskGenAlgorithm;
|
X509_ALGOR *maskGenAlgorithm;
|
||||||
ASN1_INTEGER *saltLength;
|
ASN1_INTEGER *saltLength;
|
||||||
ASN1_INTEGER *trailerField;
|
ASN1_INTEGER *trailerField;
|
||||||
/* Decoded hash algorithm from maskGenAlgorithm */
|
/* Decoded hash algorithm from maskGenAlgorithm */
|
||||||
X509_ALGOR *maskHash;
|
X509_ALGOR *maskHash;
|
||||||
} RSA_PSS_PARAMS;
|
};
|
||||||
|
|
||||||
DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS)
|
DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generated by util/mkerr.pl DO NOT EDIT
|
* Generated by util/mkerr.pl DO NOT EDIT
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
* this file except in compliance with the License. You can obtain a copy
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
#ifndef HEADER_RSAERR_H
|
#ifndef HEADER_RSAERR_H
|
||||||
# define HEADER_RSAERR_H
|
# define HEADER_RSAERR_H
|
||||||
|
|
||||||
|
# ifndef HEADER_SYMHACKS_H
|
||||||
|
# include <openssl/symhacks.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifdef __cplusplus
|
# ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
# endif
|
# endif
|
||||||
@@ -126,6 +130,7 @@ int ERR_load_RSA_strings(void);
|
|||||||
# define RSA_R_KEY_PRIME_NUM_INVALID 165
|
# define RSA_R_KEY_PRIME_NUM_INVALID 165
|
||||||
# define RSA_R_KEY_SIZE_TOO_SMALL 120
|
# define RSA_R_KEY_SIZE_TOO_SMALL 120
|
||||||
# define RSA_R_LAST_OCTET_INVALID 134
|
# define RSA_R_LAST_OCTET_INVALID 134
|
||||||
|
# define RSA_R_MISSING_PRIVATE_KEY 179
|
||||||
# define RSA_R_MGF1_DIGEST_NOT_ALLOWED 152
|
# define RSA_R_MGF1_DIGEST_NOT_ALLOWED 152
|
||||||
# define RSA_R_MODULUS_TOO_LARGE 105
|
# define RSA_R_MODULUS_TOO_LARGE 105
|
||||||
# define RSA_R_MP_COEFFICIENT_NOT_INVERSE_OF_R 168
|
# define RSA_R_MP_COEFFICIENT_NOT_INVERSE_OF_R 168
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||||
* Copyright 2005 Nokia. All rights reserved.
|
* Copyright 2005 Nokia. All rights reserved.
|
||||||
*
|
*
|
||||||
@@ -1364,24 +1364,24 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
|||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||||
# define SSL_CTX_set1_chain_cert_store(ctx,st) \
|
# define SSL_CTX_set1_chain_cert_store(ctx,st) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||||
# define SSL_set0_chain(ctx,sk) \
|
# define SSL_set0_chain(s,sk) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN,0,(char *)(sk))
|
SSL_ctrl(s,SSL_CTRL_CHAIN,0,(char *)(sk))
|
||||||
# define SSL_set1_chain(ctx,sk) \
|
# define SSL_set1_chain(s,sk) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN,1,(char *)(sk))
|
SSL_ctrl(s,SSL_CTRL_CHAIN,1,(char *)(sk))
|
||||||
# define SSL_add0_chain_cert(ctx,x509) \
|
# define SSL_add0_chain_cert(s,x509) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN_CERT,0,(char *)(x509))
|
SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,0,(char *)(x509))
|
||||||
# define SSL_add1_chain_cert(ctx,x509) \
|
# define SSL_add1_chain_cert(s,x509) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN_CERT,1,(char *)(x509))
|
SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,1,(char *)(x509))
|
||||||
# define SSL_get0_chain_certs(ctx,px509) \
|
# define SSL_get0_chain_certs(s,px509) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_GET_CHAIN_CERTS,0,px509)
|
SSL_ctrl(s,SSL_CTRL_GET_CHAIN_CERTS,0,px509)
|
||||||
# define SSL_clear_chain_certs(ctx) \
|
# define SSL_clear_chain_certs(s) \
|
||||||
SSL_set0_chain(ctx,NULL)
|
SSL_set0_chain(s,NULL)
|
||||||
# define SSL_build_cert_chain(s, flags) \
|
# define SSL_build_cert_chain(s, flags) \
|
||||||
SSL_ctrl(s,SSL_CTRL_BUILD_CERT_CHAIN, flags, NULL)
|
SSL_ctrl(s,SSL_CTRL_BUILD_CERT_CHAIN, flags, NULL)
|
||||||
# define SSL_select_current_cert(ctx,x509) \
|
# define SSL_select_current_cert(s,x509) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509))
|
SSL_ctrl(s,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509))
|
||||||
# define SSL_set_current_cert(ctx,op) \
|
# define SSL_set_current_cert(s,op) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_CURRENT_CERT, op, NULL)
|
SSL_ctrl(s,SSL_CTRL_SET_CURRENT_CERT, op, NULL)
|
||||||
# define SSL_set0_verify_cert_store(s,st) \
|
# define SSL_set0_verify_cert_store(s,st) \
|
||||||
SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st))
|
SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||||
# define SSL_set1_verify_cert_store(s,st) \
|
# define SSL_set1_verify_cert_store(s,st) \
|
||||||
@@ -1390,34 +1390,34 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
|||||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||||
# define SSL_set1_chain_cert_store(s,st) \
|
# define SSL_set1_chain_cert_store(s,st) \
|
||||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||||
# define SSL_get1_groups(ctx, s) \
|
# define SSL_get1_groups(s, glist) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_GET_GROUPS,0,(char *)(s))
|
SSL_ctrl(s,SSL_CTRL_GET_GROUPS,0,(int*)(glist))
|
||||||
# define SSL_CTX_set1_groups(ctx, glist, glistlen) \
|
# define SSL_CTX_set1_groups(ctx, glist, glistlen) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(int *)(glist))
|
||||||
# define SSL_CTX_set1_groups_list(ctx, s) \
|
# define SSL_CTX_set1_groups_list(ctx, s) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
|
||||||
# define SSL_set1_groups(ctx, glist, glistlen) \
|
# define SSL_set1_groups(s, glist, glistlen) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
SSL_ctrl(s,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
||||||
# define SSL_set1_groups_list(ctx, s) \
|
# define SSL_set1_groups_list(s, str) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
|
SSL_ctrl(s,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(str))
|
||||||
# define SSL_get_shared_group(s, n) \
|
# define SSL_get_shared_group(s, n) \
|
||||||
SSL_ctrl(s,SSL_CTRL_GET_SHARED_GROUP,n,NULL)
|
SSL_ctrl(s,SSL_CTRL_GET_SHARED_GROUP,n,NULL)
|
||||||
# define SSL_CTX_set1_sigalgs(ctx, slist, slistlen) \
|
# define SSL_CTX_set1_sigalgs(ctx, slist, slistlen) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
||||||
# define SSL_CTX_set1_sigalgs_list(ctx, s) \
|
# define SSL_CTX_set1_sigalgs_list(ctx, s) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s))
|
||||||
# define SSL_set1_sigalgs(ctx, slist, slistlen) \
|
# define SSL_set1_sigalgs(s, slist, slistlen) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
SSL_ctrl(s,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
||||||
# define SSL_set1_sigalgs_list(ctx, s) \
|
# define SSL_set1_sigalgs_list(s, str) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s))
|
SSL_ctrl(s,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(str))
|
||||||
# define SSL_CTX_set1_client_sigalgs(ctx, slist, slistlen) \
|
# define SSL_CTX_set1_client_sigalgs(ctx, slist, slistlen) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist))
|
||||||
# define SSL_CTX_set1_client_sigalgs_list(ctx, s) \
|
# define SSL_CTX_set1_client_sigalgs_list(ctx, s) \
|
||||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s))
|
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s))
|
||||||
# define SSL_set1_client_sigalgs(ctx, slist, slistlen) \
|
# define SSL_set1_client_sigalgs(s, slist, slistlen) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,clistlen,(int *)(slist))
|
SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist))
|
||||||
# define SSL_set1_client_sigalgs_list(ctx, s) \
|
# define SSL_set1_client_sigalgs_list(s, str) \
|
||||||
SSL_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s))
|
SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(str))
|
||||||
# define SSL_get0_certificate_types(s, clist) \
|
# define SSL_get0_certificate_types(s, clist) \
|
||||||
SSL_ctrl(s, SSL_CTRL_GET_CLIENT_CERT_TYPES, 0, (char *)(clist))
|
SSL_ctrl(s, SSL_CTRL_GET_CLIENT_CERT_TYPES, 0, (char *)(clist))
|
||||||
# define SSL_CTX_set1_client_certificate_types(ctx, clist, clistlen) \
|
# define SSL_CTX_set1_client_certificate_types(ctx, clist, clistlen) \
|
||||||
@@ -2139,7 +2139,7 @@ size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx);
|
|||||||
# define SSL_cache_hit(s) SSL_session_reused(s)
|
# define SSL_cache_hit(s) SSL_session_reused(s)
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
__owur int SSL_session_reused(SSL *s);
|
__owur int SSL_session_reused(const SSL *s);
|
||||||
__owur int SSL_is_server(const SSL *s);
|
__owur int SSL_is_server(const SSL *s);
|
||||||
|
|
||||||
__owur __owur SSL_CONF_CTX *SSL_CONF_CTX_new(void);
|
__owur __owur SSL_CONF_CTX *SSL_CONF_CTX_new(void);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||||
*
|
*
|
||||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||||
@@ -292,6 +292,9 @@ extern "C" {
|
|||||||
|
|
||||||
# define TLS1_FLAGS_STATELESS 0x0800
|
# define TLS1_FLAGS_STATELESS 0x0800
|
||||||
|
|
||||||
|
/* Set if extended master secret extension required on renegotiation */
|
||||||
|
# define TLS1_FLAGS_REQUIRED_EXTMS 0x1000
|
||||||
|
|
||||||
# define SSL3_MT_HELLO_REQUEST 0
|
# define SSL3_MT_HELLO_REQUEST 0
|
||||||
# define SSL3_MT_CLIENT_HELLO 1
|
# define SSL3_MT_CLIENT_HELLO 1
|
||||||
# define SSL3_MT_SERVER_HELLO 2
|
# define SSL3_MT_SERVER_HELLO 2
|
||||||
|
|||||||
Loaded 100 of 191 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user