diff --git a/.github/workflows/pluvia-pr-check.yml b/.github/workflows/android_ci.yml similarity index 64% rename from .github/workflows/pluvia-pr-check.yml rename to .github/workflows/android_ci.yml index c7a45e74b..4e57bda1f 100644 --- a/.github/workflows/pluvia-pr-check.yml +++ b/.github/workflows/android_ci.yml @@ -1,38 +1,46 @@ -name: Pull Request build check +name: Android CI on: + push: + branches: [ "master" ] pull_request: branches: [ "master" ] - paths-ignore: - - '**.md' - - '.gitignore' - - 'keyvalues/**' - - 'media/**' - - '.github/ISSUE_TEMPLATE/**' + workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - - name: Checking out GameNative + - name: Checkout code uses: actions/checkout@v4 + - name: Setup Java 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' + - name: Inject credentials run: | - cat < local.properties + cat <> gradle.properties POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }} POSTHOG_HOST=${{ secrets.POSTHOG_HOST }} SUPABASE_URL=${{ secrets.SUPABASE_URL }} SUPABASE_KEY=${{ secrets.SUPABASE_KEY }} EOF + - name: Validate Gradle wrapper uses: gradle/actions/wrapper-validation@v4 + - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Run unit tests - run: ./gradlew :app:testDebugUnitTest + + - name: Build Release APK + run: ./gradlew assembleRelease + + - name: Upload Release APK + uses: actions/upload-artifact@v4 + with: + name: app-release + path: app/build/outputs/apk/release/app-release.apk diff --git a/.github/workflows/app-release-signed.yml b/.github/workflows/app-release-signed.yml index e8d2918a8..ec186af03 100644 --- a/.github/workflows/app-release-signed.yml +++ b/.github/workflows/app-release-signed.yml @@ -1,4 +1,4 @@ -name: Android CI +name: Android CI (Debug) on: push: @@ -13,58 +13,32 @@ on: jobs: build: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: set up Java 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: gradle + - name: Validate Gradle wrapper uses: gradle/actions/wrapper-validation@v4 + - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Configure Keystore - env: - KEYSTORE: ${{ secrets.KEYSTORE }} - KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - run: | - mkdir -p app/keystores - echo "$KEYSTORE" | base64 --decode > app/keystores/keystore - echo "storeFile=keystores/keystore" >> app/keystores/keystore.properties - echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> app/keystores/keystore.properties - echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> app/keystores/keystore.properties - echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> app/keystores/keystore.properties - - - name: Configure Production Keystore - env: - PROD_KEYSTORE: ${{ secrets.PROD_KEYSTORE }} - PROD_KEYSTORE_KEY_ALIAS: ${{ secrets.PROD_KEYSTORE_KEY_ALIAS }} - PROD_KEYSTORE_KEY_PASSWORD: ${{ secrets.PROD_KEYSTORE_KEY_PASSWORD }} - PROD_KEYSTORE_STORE_PASSWORD: ${{ secrets.PROD_KEYSTORE_STORE_PASSWORD }} - run: | - mkdir -p app/keystores - echo "$PROD_KEYSTORE" | base64 --decode > app/keystores/prod.keystore - cat < app/keystores/prod-keystore.properties - keyAlias=$PROD_KEYSTORE_KEY_ALIAS - keyPassword=$PROD_KEYSTORE_KEY_PASSWORD - storePassword=$PROD_KEYSTORE_STORE_PASSWORD - EOF - + # REPLACED: Secrets are removed. Empty strings "" prevent the Java syntax error. - name: Inject credentials run: | cat < local.properties - POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }} - POSTHOG_HOST=${{ secrets.POSTHOG_HOST }} - SUPABASE_URL=${{ secrets.SUPABASE_URL }} - SUPABASE_KEY=${{ secrets.SUPABASE_KEY }} + POSTHOG_API_KEY="" + POSTHOG_HOST="" + SUPABASE_URL="" + SUPABASE_KEY="" EOF - name: Install Android SDK Build Tools @@ -75,52 +49,15 @@ jobs: fi yes | "$SDKMANAGER" --install "build-tools;34.0.0" + # CHANGED: Switched to assembleDebug so we don't need signing keys - name: Build with Gradle - run: ./gradlew :app:bundleRelease - - - name: Extract APK from Bundle - run: | - java -jar tools/bundletool-all-1.17.2.jar build-apks --bundle=app/build/outputs/bundle/release/app-release.aab --output=app-release.apks --mode=universal - unzip -o app-release.apks universal.apk - - - name: Copy lineage file - run: | - mkdir -p app/keystores - cp prod-debug.lineage app/keystores/prod-debug.lineage - - - name: Dual sign universal APK - env: - DEBUG_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - DEBUG_KEY_PASS: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - DEBUG_STORE_PASS: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - PROD_ALIAS: ${{ secrets.PROD_KEYSTORE_KEY_ALIAS }} - PROD_KEY_PASS: ${{ secrets.PROD_KEYSTORE_KEY_PASSWORD }} - PROD_STORE_PASS: ${{ secrets.PROD_KEYSTORE_STORE_PASSWORD }} - run: | - BUILD_TOOLS_DIR="${ANDROID_HOME}/build-tools/34.0.0" - if [ ! -d "$BUILD_TOOLS_DIR" ]; then - BUILD_TOOLS_DIR="${ANDROID_SDK_ROOT}/build-tools/34.0.0" - fi - "${BUILD_TOOLS_DIR}/apksigner" sign \ - --lineage app/keystores/prod-debug.lineage \ - --ks app/keystores/keystore \ - --ks-key-alias "$DEBUG_ALIAS" \ - --ks-pass pass:$DEBUG_STORE_PASS \ - --key-pass pass:$DEBUG_KEY_PASS \ - --next-signer --ks app/keystores/prod.keystore \ - --ks-key-alias "$PROD_ALIAS" \ - --ks-pass pass:$PROD_STORE_PASS \ - --key-pass pass:$PROD_KEY_PASS \ - --out universal-signed.apk \ - universal.apk - mv universal-signed.apk universal.apk + run: ./gradlew assembleDebug - name: Upload Artifact - id: upload_app uses: actions/upload-artifact@v4 with: - name: app-release-signed - path: ${{ github.workspace }}/universal.apk + name: app-debug + path: app/build/outputs/apk/debug/app-debug.apk - name: Build changelog text id: changelog @@ -129,11 +66,4 @@ jobs: echo 'log<>"$GITHUB_OUTPUT" echo "$text" >>"$GITHUB_OUTPUT" echo 'EOF' >>"$GITHUB_OUTPUT" - - - name: Post to Discord - env: - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - run: | - artifact_url="https://nightly.link/utkarshdalal/GameNative/actions/runs/${{ github.run_id }}/app-release-signed.zip" - payload='{"content":"**New Android build**\nDownload: '"$artifact_url"'\n\nChanges:\n'"${{ steps.changelog.outputs.log }}"'"}' - curl -H 'Content-Type: application/json' -d "$payload" "$DISCORD_WEBHOOK_URL" + diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml deleted file mode 100644 index be62bc1fa..000000000 --- a/.github/workflows/tagged-release.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: Create tagged prerelease - -on: - push: - tags: - - "v*" - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: set up Java 17 - uses: actions/setup-java@v4 - with: - java-version: "17" - distribution: "temurin" - cache: gradle - - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v4 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Configure Keystore - env: - KEYSTORE: ${{ secrets.KEYSTORE }} - KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - run: | - mkdir -p app/keystores - echo "$KEYSTORE" | base64 --decode > app/keystores/keystore - echo "storeFile=keystores/keystore" >> app/keystores/keystore.properties - echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> app/keystores/keystore.properties - echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> app/keystores/keystore.properties - echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> app/keystores/keystore.properties - - - name: Configure Production Keystore - env: - PROD_KEYSTORE: ${{ secrets.PROD_KEYSTORE }} - PROD_KEYSTORE_KEY_ALIAS: ${{ secrets.PROD_KEYSTORE_KEY_ALIAS }} - PROD_KEYSTORE_KEY_PASSWORD: ${{ secrets.PROD_KEYSTORE_KEY_PASSWORD }} - PROD_KEYSTORE_STORE_PASSWORD: ${{ secrets.PROD_KEYSTORE_STORE_PASSWORD }} - run: | - mkdir -p app/keystores - echo "$PROD_KEYSTORE" | base64 --decode > app/keystores/prod.keystore - cat < app/keystores/prod-keystore.properties - keyAlias=$PROD_KEYSTORE_KEY_ALIAS - keyPassword=$PROD_KEYSTORE_KEY_PASSWORD - storePassword=$PROD_KEYSTORE_STORE_PASSWORD - EOF - - - name: Inject credentials - run: | - cat < local.properties - POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }} - POSTHOG_HOST=${{ secrets.POSTHOG_HOST }} - SUPABASE_URL=${{ secrets.SUPABASE_URL }} - SUPABASE_KEY=${{ secrets.SUPABASE_KEY }} - EOF - - - name: Install Android SDK Build Tools - run: | - SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" - if [ ! -x "$SDKMANAGER" ]; then - SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" - fi - yes | "$SDKMANAGER" --install "build-tools;34.0.0" - - - name: Build with Gradle - run: ./gradlew :app:bundleRelease - - - name: Extract APK from Bundle - run: | - java -jar tools/bundletool-all-1.17.2.jar build-apks \ - --bundle=app/build/outputs/bundle/release/app-release.aab \ - --output=app-release.apks \ - --mode=universal - unzip -o app-release.apks universal.apk - - - name: Copy lineage file - run: | - mkdir -p app/keystores - cp prod-debug.lineage app/keystores/prod-debug.lineage - - - name: Dual sign universal APK - env: - DEBUG_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - DEBUG_KEY_PASS: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - DEBUG_STORE_PASS: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - PROD_ALIAS: ${{ secrets.PROD_KEYSTORE_KEY_ALIAS }} - PROD_KEY_PASS: ${{ secrets.PROD_KEYSTORE_KEY_PASSWORD }} - PROD_STORE_PASS: ${{ secrets.PROD_KEYSTORE_STORE_PASSWORD }} - run: | - BUILD_TOOLS_DIR="${ANDROID_HOME}/build-tools/34.0.0" - if [ ! -d "$BUILD_TOOLS_DIR" ]; then - BUILD_TOOLS_DIR="${ANDROID_SDK_ROOT}/build-tools/34.0.0" - fi - "${BUILD_TOOLS_DIR}/apksigner" sign \ - --lineage app/keystores/prod-debug.lineage \ - --ks app/keystores/keystore \ - --ks-key-alias "$DEBUG_ALIAS" \ - --ks-pass pass:$DEBUG_STORE_PASS \ - --key-pass pass:$DEBUG_KEY_PASS \ - --next-signer --ks app/keystores/prod.keystore \ - --ks-key-alias "$PROD_ALIAS" \ - --ks-pass pass:$PROD_STORE_PASS \ - --key-pass pass:$PROD_KEY_PASS \ - --out universal-signed.apk \ - universal.apk - mv universal-signed.apk universal.apk - - - name: Upload APK for next job - uses: actions/upload-artifact@v4 - with: - name: universal-apk - path: universal.apk - - release: - needs: build - runs-on: ubuntu-latest - - steps: - - name: Download built APK - uses: actions/download-artifact@v4 - with: - name: universal-apk - path: ./ - - - name: Rename APK - run: | - TAG="${GITHUB_REF#refs/tags/}" - mv universal.apk "gamenative-$TAG.apk" - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - name: ${{ github.ref_name }} - prerelease: true - generate_release_notes: true - files: gamenative-${{ github.ref_name }}.apk - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af29c3906..137b452eb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,7 +52,7 @@ android:scheme="home" /> - + diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index a96062aa0..85951521e 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -186,6 +187,37 @@ fun XServerScreen( ContainerUtils.getContainer(context, appId) } + // Hyper Frames (OnePlus 15 / 165Hz support) + LaunchedEffect(Unit) { + val graphicsDriverConfig = KeyValueSet(container.getGraphicsDriverConfig()) + val isAdrenotoolsTurnip = graphicsDriverConfig.get("adrenotoolsTurnip", "1") + val driverVersion = graphicsDriverConfig.get("version", DefaultVersion.WRAPPER) + + // Check if using system driver and adreno toolkit is disabled + val isWrapperOrSystem = !listOf("turnip", "virgl", "vortek", "adreno", "sd-8-elite").contains(container.graphicsDriver) + + if (isWrapperOrSystem && driverVersion == "System" && isAdrenotoolsTurnip == "0") { + val activity = context as? Activity + activity?.let { act -> + act.window?.let { window -> + val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + act.display + } else { + @Suppress("DEPRECATION") + act.windowManager.defaultDisplay + } + + display?.supportedModes?.maxByOrNull { it.refreshRate }?.let { maxMode -> + Timber.i("Hyper Frames: Setting preferred display mode to ${maxMode.modeId} (${maxMode.refreshRate} Hz)") + val layoutParams = window.attributes + layoutParams.preferredDisplayModeId = maxMode.modeId + window.attributes = layoutParams + } + } + } + } + } + val xServerState = rememberSaveable(stateSaver = XServerState.Saver) { mutableStateOf( XServerState( diff --git a/app/src/main/java/app/gamenative/utils/ShortcutUtils.kt b/app/src/main/java/app/gamenative/utils/ShortcutUtils.kt index 849baa724..c4c1d832c 100644 --- a/app/src/main/java/app/gamenative/utils/ShortcutUtils.kt +++ b/app/src/main/java/app/gamenative/utils/ShortcutUtils.kt @@ -12,6 +12,7 @@ import android.graphics.RectF import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Icon import android.os.Build +import app.gamenative.BuildConfig import app.gamenative.MainActivity import app.gamenative.R import coil.ImageLoader @@ -88,7 +89,7 @@ internal suspend fun createPinnedShortcut(context: Context, gameId: Int, label: val appContext = context.applicationContext val shortcutManager = appContext.getSystemService(ShortcutManager::class.java) - val intent = Intent("app.gamenative.LAUNCH_GAME").apply { + val intent = Intent(BuildConfig.APPLICATION_ID + ".LAUNCH_GAME").apply { setClass(appContext, MainActivity::class.java) putExtra("app_id", gameId) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/app/src/main/java/com/winlator/container/Container.java b/app/src/main/java/com/winlator/container/Container.java index 21dc53d7d..e8041bde6 100644 --- a/app/src/main/java/com/winlator/container/Container.java +++ b/app/src/main/java/com/winlator/container/Container.java @@ -20,6 +20,8 @@ import java.io.File; import java.util.Iterator; +import app.gamenative.BuildConfig; + public class Container { public enum XrControllerMapping { BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, BUTTON_GRIP, BUTTON_TRIGGER, @@ -38,14 +40,14 @@ public enum XrControllerMapping { public static final String DEFAULT_WINCOMPONENTS = "direct3d=1,directsound=1,directmusic=0,directshow=0,directplay=0,vcrun2010=1,wmdecoder=1,opengl=0"; public static final String FALLBACK_WINCOMPONENTS = "direct3d=1,directsound=1,directmusic=1,directshow=1,directplay=1,vcrun2010=1,wmdecoder=1,opengl=0"; public static final String[] MEDIACONV_ENV_VARS = { - "MEDIACONV_AUDIO_DUMP_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/audio.dmp", - "MEDIACONV_VIDEO_DUMP_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/video.dmp", - "MEDIACONV_VIDEO_TRANSCODED_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/transcoded.mkv", - "MEDIACONV_AUDIO_TRANSCODED_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/transcoded.wav", - "MEDIACONV_BLANK_AUDIO_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/blank.wav", - "MEDIACONV_BLANK_VIDEO_FILE=/data/data/app.gamenative/files/imagefs/home/xuser/blank.mkv", + "MEDIACONV_AUDIO_DUMP_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/audio.dmp", + "MEDIACONV_VIDEO_DUMP_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/video.dmp", + "MEDIACONV_VIDEO_TRANSCODED_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/transcoded.mkv", + "MEDIACONV_AUDIO_TRANSCODED_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/transcoded.wav", + "MEDIACONV_BLANK_AUDIO_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/blank.wav", + "MEDIACONV_BLANK_VIDEO_FILE=/data/data/" + BuildConfig.APPLICATION_ID + "/files/imagefs/home/xuser/blank.mkv", }; - public static final String DEFAULT_DRIVES = "D:"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"E:/data/data/app.gamenative/storage"; + public static final String DEFAULT_DRIVES = "D:"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"E:/data/data/" + BuildConfig.APPLICATION_ID + "/storage"; public static final String DEFAULT_VARIANT = DefaultVersion.VARIANT; public static final String DEFAULT_WINE_VERSION = DefaultVersion.WINE_VERSION; public static final byte STARTUP_SELECTION_NORMAL = 0;