Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
5d56d79
Add Gradle wrapper files and update settings for Android project
Kief5555 May 14, 2026
f97710e
feat: Enhance streaming experience and UI updates
Kief5555 May 16, 2026
d0771a6
feat: Enhance session proxy settings and ad playback functionality
Kief5555 May 16, 2026
36149af
feat: Implement QR code generation and enhance streaming stats
Kief5555 May 16, 2026
97b644e
feat: Add functionality to remove completed ads from session state an…
Kief5555 May 16, 2026
02a7bfe
Refactor UI colors and enhance device login experience
Kief5555 May 16, 2026
05e8eff
feat: Enhance device code login support and improve session handling
Kief5555 May 16, 2026
fbd669d
feat: Replace loading skeleton with pulsing animation for game images
Kief5555 May 16, 2026
2eed4bd
feat: Enhance OpenNowViewModel with account switching and stream retu…
Kief5555 May 17, 2026
d2036a5
Potential fix for pull request finding
Kief5555 May 17, 2026
d525803
feat: Enhance stream resolution options and implement membership tier…
Kief5555 May 17, 2026
3b7d1c1
Merge branch 'android-native' of https://github.com/OpenCloudGaming/O…
Kief5555 May 17, 2026
49f6847
feat: Remove release artifacts and update .gitignore for cleaner builds
Kief5555 May 17, 2026
380a69a
feat: Update minimum SDK version and increment version code; enhance …
Kief5555 May 17, 2026
5963880
feat: Refactor token management to use milliseconds for refresh windo…
Kief5555 May 18, 2026
c722a8f
feat: Enhance WebRTC codec handling and input channel management
Kief5555 May 23, 2026
3837fae
feat: Refactor mouse capture handling and enhance stream settings adj…
Kief5555 May 24, 2026
d9cbf2c
feat: Implement display refresh rate management and enhance stream se…
Kief5555 May 31, 2026
556bdfe
Add vector drawable icons for various game stores and implement unit …
Kief5555 Jun 14, 2026
4898a2e
feat: Add HDR support and enhance haptic feedback for gamepad control…
Kief5555 Jun 15, 2026
c0a866d
feat: Enhance sorting and filtering UI with improved layout and funct…
Kief5555 Jun 15, 2026
b81beaa
feat: Implement settings reset and cache clearing functionality; enha…
Kief5555 Jun 16, 2026
6bc4504
Add new strings for empty search states and filters; enhance tests fo…
Kief5555 Jun 22, 2026
82a146c
feat: Implement Android update functionality
Kief5555 Jun 23, 2026
6ead986
Add unit test for OpenNow analytics configuration
Kief5555 Jun 26, 2026
e5d36e6
feat: Update versioning, enhance stream settings, and improve UI/UX
Kief5555 Jun 27, 2026
9e8d0ca
feat: Add donation section and debug event logging
Kief5555 Jun 27, 2026
97e839c
Implement persistent storage management and game store connectors for…
Kief5555 Jun 29, 2026
f4f736c
Fix(android): add storage addon filter and connector state/token fixe…
Kief5555 Jun 29, 2026
6dc97ac
feat: Update version and enhance audio cue functionality
Kief5555 Jun 29, 2026
3845723
Merge branch 'android-native' of https://github.com/OpenCloudGaming/O…
Kief5555 Jun 29, 2026
8204694
Add in-app store connector connect/disconnect actions (#548)
Kief5555 Jun 29, 2026
41a95d2
feat(android): update version code to 16 and version name to 0.6.1
Kief5555 Jun 29, 2026
1353777
Merge branch 'android-native' of https://github.com/OpenCloudGaming/O…
Kief5555 Jun 29, 2026
5f808f7
Fix Android account linking and add missing Steam provider (#550)
Kief5555 Jun 29, 2026
859d909
Fix Android connector refresh polling (#553)
Kief5555 Jun 29, 2026
9f091fc
Fix account-link ALS 401 error and add toasts (#554)
Kief5555 Jun 29, 2026
b702052
Fix Android ALS endpoint and client ID to match desktop (#559)
Kief5555 Jun 29, 2026
0be6ebc
feat: Add settings screen with various configuration options
Kief5555 Jun 30, 2026
2af413a
feat: Add Android update notice handling and improve codec negotiation
Kief5555 Jul 1, 2026
6abb5fa
chore: Remove unused Kotlin compiler session file
Kief5555 Jul 1, 2026
26c0aa5
feat: Enhance storage usage progress indicator with dynamic height an…
Kief5555 Jul 1, 2026
36b311f
feat: Enhance stream resolution handling and gamepad input support
Kief5555 Jul 1, 2026
e5eb361
feat: Add play time stats panel and enhance session management
Kief5555 Jul 3, 2026
405fcda
feat: Implement APK update support and enhance update management for …
Kief5555 Jul 4, 2026
f695e88
feat: Enhance settings UI with descriptions and add account provider …
Kief5555 Jul 5, 2026
0a3b94c
feat: Enhance diagnostic logging and redaction for sensitive information
Kief5555 Jul 5, 2026
7ed7106
Refactor OpenNowViewModel for improved debugging and diagnostics
Kief5555 Jul 5, 2026
b19868b
feat: Refactor SDP handling to improve candidate endpoint resolution …
Kief5555 Jul 5, 2026
3c95f3f
Refactor code structure for improved readability and maintainability
Kief5555 Jul 6, 2026
bc48bc4
feat: Enhance streaming settings and diagnostics
Kief5555 Jul 8, 2026
4496299
Refactor mouse assist functionality and update related tests
Kief5555 Jul 9, 2026
9b56102
Refactor catalog background image handling and improve haptics parsing
Kief5555 Jul 10, 2026
ff2d029
feat: Update version code and name, enhance controller navigation han…
Kief5555 Jul 11, 2026
9bac3cb
feat: Update version code to 34 and name to 0.7.9; enhance streaming …
Kief5555 Jul 11, 2026
1944eb4
feat: Update version code to 35 and name to 0.8.0; improve SurfaceVie…
Kief5555 Jul 11, 2026
b2c7b28
feat: Implement nextSessionAdId function and refactor queue ad handli…
Kief5555 Jul 12, 2026
c619185
feat: Update version code to 36 and name to 0.8.1
Kief5555 Jul 12, 2026
4153ed6
feat: Add appCategory to manifest and adjust windowSoftInputMode in M…
Kief5555 Jul 12, 2026
a2e6168
Add unit tests for AndroidStreamKeepAliveNotifier and CellularNetwork…
Kief5555 Jul 12, 2026
b1e5831
feat(android): redesign virtual touch controller, secure auth storage…
Chizuui Jul 12, 2026
28b7f10
Merge pull request #634 from Chizuui/android-native
Kief5555 Jul 13, 2026
01e58c5
feat(android): implement multi-style touch overlay, unified sliding D…
Chizuui Jul 14, 2026
f279685
Merge pull request #641 from Chizuui/dev-android-native
Kief5555 Jul 15, 2026
f5f9cd3
Improve Android auth refresh and stream recovery
Kief5555 Jul 15, 2026
5fd8ede
Add tests for launch errors, ownership, stream settings, and local TV…
Kief5555 Jul 16, 2026
59aef68
Refactor stream handling and improve artwork logic
Kief5555 Jul 16, 2026
9eeb55b
fix(android): improve virtual stick pointer handling and state manage…
Kief5555 Jul 16, 2026
714a0c5
fix(android): resolve input thread lag, TV controller navigation, hal…
Chizuui Jul 16, 2026
26f3ec6
Merge pull request #646 from Chizuui/dev-android-native-pr
Kief5555 Jul 16, 2026
d5c8d22
fix(android): fix virtual stick getting stuck on release outside stic…
Chizuui Jul 17, 2026
d3268b8
fix(android): fix analog stick held state not cleared when touch rele…
Chizuui Jul 17, 2026
d87e837
Merge pull request #647 from Chizuui/android-native
Kief5555 Jul 17, 2026
9e153f4
fix(android): update version code and name, enhance game card scaling…
Kief5555 Jul 17, 2026
f90b3ec
Merge branch 'android-native' of https://github.com/OpenCloudGaming/O…
Kief5555 Jul 17, 2026
6e55d55
feat: TV and input experience improvements
Chizuui Jul 17, 2026
49b6b2a
Merge pull request #648 from Chizuui/pr-tv-input-improvements
Kief5555 Jul 17, 2026
a6cb5d4
Refactor settings UI and enhance controller focus handling
Kief5555 Jul 18, 2026
d743870
feat: Update version and enhance HDR support
Kief5555 Jul 18, 2026
7b88bad
feat: Add microphone support and bug reporting functionality
Kief5555 Jul 19, 2026
bf01a98
feat: Enhance resolution handling for 1440p streams and add correspon…
Kief5555 Jul 19, 2026
0b27de5
feat: Update version code to 47 and version name to 0.9.2
Kief5555 Jul 19, 2026
40b2194
feat: add native low latency video decoder wrapper with MediaTek support
Chizuui Jul 19, 2026
086d9c9
Refactor streaming settings and performance warnings
Kief5555 Jul 20, 2026
aa44fa1
Merge branch 'android-native' of https://github.com/OpenCloudGaming/O…
Kief5555 Jul 20, 2026
fb02a34
feat: implement Android controller slot management and enhance input …
Kief5555 Jul 20, 2026
fb72c35
feat: update version code to 49 and version name to 0.9.4; add pinch …
Kief5555 Jul 20, 2026
fac02fd
feat: update version code to 51 and version name to 0.9.6; refactor G…
Kief5555 Jul 20, 2026
19d848e
Refactor and enhance session reporting and stream settings tests
Kief5555 Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ gfn_tokens.json

# Test files
test/
!android/app/src/test/
!android/app/src/test/**

package-lock.json
opennow-stable/package-lock.json
Expand All @@ -56,3 +58,6 @@ release-notes.md
result
.deriveddata/
.deriveddata-device/

/android/app/.cxx
/android/app/release/
8 changes: 8 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.gradle/
.idea/
build/
local.properties
captures/
*.iml
app/build/
app/release/
17 changes: 17 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OpenNOW Native Android

This folder is a standalone Android Studio project for the native Kotlin / Jetpack Compose OpenNOW target.

Open it from Android Studio with **File > Open > `OpenNOW/android`**. Android Studio will install/use the required Gradle, Android SDK, CMake, and NDK components from the project configuration.

## Build Targets

- `:app:assembleDebug` builds a debug APK.
- `:app:assembleRelease` builds a release APK after signing config is added locally.

## Runtime Notes

- UI is native Compose.
- GFN auth, catalog, subscription, CloudMatch session creation/polling/claim/stop, signaling, and input packet behavior are implemented in Kotlin from the Electron project contracts.
- Streaming uses Android WebRTC plus hardware MediaCodec probing. The bundled `opennow_native` JNI library exposes native runtime diagnostics and keeps the NDK/CMake path wired for media-sensitive code.
- Queue ad metadata is preserved in `SessionInfo`; ad playback can use the included Media3 dependency when the server returns an ad media URL.
105 changes: 105 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.kotlin.plugin.serialization")
}

android {
namespace = "com.opencloudgaming.opennow"
compileSdk = 36

defaultConfig {
applicationId = "com.opencloudgaming.opennow"
minSdk = 26
targetSdk = 36
versionCode = 2
versionName = "0.3.9-native"

ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
}

externalNativeBuild {
cmake {
cppFlags += listOf("-std=c++17", "-fexceptions", "-frtti")
}
}
}

buildTypes {
debug {
isMinifyEnabled = false
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}

buildFeatures {
compose = true
buildConfig = true
}

externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}

packaging {
jniLibs {
useLegacyPackaging = false
}
resources {
excludes += setOf(
"META-INF/AL2.0",
"META-INF/LGPL2.1",
"META-INF/LICENSE*",
"META-INF/NOTICE*",
)
}
}
}

kotlin {
jvmToolchain(17)
}

dependencies {
implementation(platform("androidx.compose:compose-bom:2026.04.01"))
androidTestImplementation(platform("androidx.compose:compose-bom:2026.04.01"))

implementation("androidx.activity:activity-compose:1.11.0")
implementation("androidx.browser:browser:1.9.0")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
implementation("androidx.media3:media3-common:1.9.0")
implementation("androidx.media3:media3-exoplayer:1.9.0")
implementation("androidx.media3:media3-ui:1.9.0")

implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("io.github.webrtc-sdk:android:144.7559.05")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")

debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")

testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}
6 changes: 6 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-keep class org.webrtc.** { *; }
-keep class org.jni_zero.** { *; }
-keep class kotlinx.serialization.** { *; }
-keepclassmembers class com.opencloudgaming.opennow.** {
@kotlinx.serialization.Serializable *;
}
Binary file added android/app/release/app-release.apk
Binary file not shown.
Binary file not shown.
Binary file not shown.
37 changes: 37 additions & 0 deletions android/app/release/output-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.opencloudgaming.opennow",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🟡 Medium] [🔵 Bug]

The checked-in release metadata declares versionCode: 1 while app config sets versionCode = 2, so the packaged artifact metadata is stale/inconsistent with the source build configuration. ```json
// android/app/release/output-metadata.json
"versionCode": 1,
"versionName": "0.3.9-native",
"outputFile": "app-release.apk"


```suggestion
      "versionCode": 2,

"versionName": "0.3.9-native",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 26
}
78 changes: 78 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.faketouch"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />

<application
android:allowBackup="true"
Comment thread
Kief5555 marked this conversation as resolved.
Outdated
android:banner="@drawable/opennow_banner"
android:hardwareAccelerated="true"
android:icon="@drawable/opennow_icon"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:resizeableActivity="true"
android:roundIcon="@drawable/opennow_icon"
android:supportsRtl="true"
android:theme="@style/Theme.OpenNOW">
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="fullSensor"
android:supportsPictureInPicture="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="opennow" android:host="launch" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="localhost" android:port="2259" />
<data android:scheme="http" android:host="localhost" android:port="6460" />
<data android:scheme="http" android:host="localhost" android:port="7119" />
<data android:scheme="http" android:host="localhost" android:port="8870" />
<data android:scheme="http" android:host="localhost" android:port="9096" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="127.0.0.1" android:port="2259" />
<data android:scheme="http" android:host="127.0.0.1" android:port="6460" />
<data android:scheme="http" android:host="127.0.0.1" android:port="7119" />
<data android:scheme="http" android:host="127.0.0.1" android:port="8870" />
<data android:scheme="http" android:host="127.0.0.1" android:port="9096" />
</intent-filter>
</activity>
</application>
</manifest>
16 changes: 16 additions & 0 deletions android/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.22.1)

project(opennow_native)

add_library(opennow_native SHARED opennow_native.cpp)

find_library(log-lib log)
find_library(android-lib android)
find_library(mediandk-lib mediandk)

target_link_libraries(
opennow_native
${android-lib}
${log-lib}
${mediandk-lib}
)
18 changes: 18 additions & 0 deletions android/app/src/main/cpp/opennow_native.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <jni.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
#include <sstream>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_opencloudgaming_opennow_NativeCodecProbe_nativeRuntimeSummary(JNIEnv *env, jobject) {
std::ostringstream out;
out << "{";
out << "\"nativeLibrary\":\"opennow_native\",";
out << "\"mediaNdk\":true,";
out << "\"rtpPacketSize\":1140,";
out << "\"inputProtocolVersion\":3";
out << "}";
const std::string value = out.str();
return env->NewStringUTF(value.c_str());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.opencloudgaming.opennow

private val READY_SESSION_STATUSES = setOf(2, 3)

internal fun isSessionAdsRequired(adState: SessionAdState?): Boolean =
adState?.sessionAdsRequired ?: (adState?.isAdsRequired == true)

internal fun sessionAdItems(adState: SessionAdState?): List<SessionAdInfo> =
adState?.sessionAds?.takeIf { it.isNotEmpty() } ?: adState?.ads.orEmpty()

internal fun shouldWaitForQueueAdPlayback(adState: SessionAdState?): Boolean =
isSessionAdsRequired(adState) && sessionAdItems(adState).isNotEmpty()

internal fun mergeQueueAdState(
previous: SessionAdState?,
next: SessionAdState?,
preserveMissingAdState: Boolean = true,
): SessionAdState? {
if (next == null) return if (preserveMissingAdState) previous else null
val shouldRestorePreviousAds =
preserveMissingAdState &&
isSessionAdsRequired(next) &&
next.serverSentEmptyAds &&
sessionAdItems(next).isEmpty() &&
sessionAdItems(previous).isNotEmpty()

return if (shouldRestorePreviousAds) {
next.copy(
sessionAds = sessionAdItems(previous),
ads = previous?.ads?.takeIf { it.isNotEmpty() } ?: sessionAdItems(previous),
)
} else {
next
}
}

internal fun mergeQueueSessionState(
previous: SessionInfo,
next: SessionInfo,
preserveMissingAdState: Boolean = true,
): SessionInfo {
if (next.status in READY_SESSION_STATUSES) return next
return next.copy(
adState = mergeQueueAdState(previous.adState, next.adState, preserveMissingAdState),
mediaConnectionInfo = next.mediaConnectionInfo ?: previous.mediaConnectionInfo,
)
}

internal fun removeSessionAdItem(adState: SessionAdState?, adId: String): SessionAdState? {
if (adState == null) return null
return adState.copy(
sessionAds = adState.sessionAds.filterNot { it.adId == adId },
ads = adState.ads.filterNot { it.adId == adId },
serverSentEmptyAds = false,
)
}

internal fun removeSessionAdItem(session: SessionInfo, adId: String): SessionInfo =
session.copy(adState = removeSessionAdItem(session.adState, adId))
Loading
Loading