mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Dev: use build product flavors for app store release (#1205)
## Description Instead of applying a code patch, this PR uses build time [product flavors](https://developer.android.com/build/build-variants#product-flavors) to define the existing default and an app store variant. This is more robust than maintaining a patch file and making sure it is applied before building. The app store variant applies the same code changes (removing install permission, requiring leanback, & setting `UpdateChecker.ACTIVE`), but via manifest placeholders/merging and `BuildConfig` entries. There are no user facing changes. ### Related issues None ### Testing Built the variants and verified manifest changes via aapt ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
9a3af225a4
commit
31465051ff
11 changed files with 91 additions and 165 deletions
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
|
|
@ -54,7 +54,7 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}"
|
||||
ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}"
|
||||
run: |
|
||||
./gradlew clean assembleRelease assembleDebug --no-daemon
|
||||
./gradlew clean assembleDefaultRelease assembleDefaultDebug --no-daemon
|
||||
|
||||
- name: Verify signatures
|
||||
run: |
|
||||
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
find app/build/outputs/apk -name '*.apk' -print0 |
|
||||
while IFS= read -r -d '' line; do
|
||||
# Wholphin-release-0.2.9-62-g5c12e03-16-arm64-v8a.apk => Wholphin-arm64-v8a.apk
|
||||
short_name="$(echo $line | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+-g[a-fA-F0-9]+-[0-9]+//')"
|
||||
short_name="$(echo $line | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+-g[a-fA-F0-9]+-[0-9]+//' | sed -E 's/-default//')"
|
||||
echo "$line => $short_name"
|
||||
cp "$line" "$short_name"
|
||||
done
|
||||
|
|
|
|||
15
.github/workflows/pr.yml
vendored
15
.github/workflows/pr.yml
vendored
|
|
@ -39,19 +39,6 @@ jobs:
|
|||
- name: Build app
|
||||
id: buildapp
|
||||
run: |
|
||||
./gradlew clean assembleDebug testDebugUnitTest --no-daemon
|
||||
./gradlew clean assembleDefaultDebug testDefaultDebugUnitTest --no-daemon
|
||||
apks=$(find app/build/outputs \( -name '*.apk' -or -name '*.aab' \) -print0 | tr '\0' ',' | sed 's/,$//')
|
||||
echo "apks=$apks" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test-patch:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-commit
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Test applying patch
|
||||
run: |
|
||||
git apply app/src/patches/play_store.patch
|
||||
git diff
|
||||
|
|
|
|||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
|
|
@ -36,22 +36,8 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}"
|
||||
ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}"
|
||||
run: |
|
||||
./gradlew clean assembleRelease --no-daemon
|
||||
./gradlew clean assembleDefaultRelease bundleAppstoreRelease --no-daemon
|
||||
|
||||
- name: Build app
|
||||
id: buildaab
|
||||
env:
|
||||
KEY_ALIAS: "${{ secrets.KEY_ALIAS }}"
|
||||
KEY_PASSWORD: "${{ secrets.KEY_PASSWORD }}"
|
||||
KEY_STORE_PASSWORD: "${{ secrets.KEY_STORE_PASSWORD }}"
|
||||
SIGNING_KEY: "${{ secrets.SIGNING_KEY }}"
|
||||
ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}"
|
||||
ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}"
|
||||
run: |
|
||||
git apply app/src/patches/play_store.patch
|
||||
./gradlew bundleRelease --no-daemon
|
||||
aab=$(find app/build/outputs -name '*.aab')
|
||||
echo "aab=$aab" >> "$GITHUB_OUTPUT"
|
||||
- name: Verify signatures
|
||||
run: |
|
||||
echo "Verify APK signatures"
|
||||
|
|
@ -62,13 +48,16 @@ jobs:
|
|||
run: |
|
||||
find app/build/outputs/apk -name '*.apk' -print0 |
|
||||
while IFS= read -r -d '' line; do
|
||||
# Wholphin-release-0.2.9-62-g5c12e03-16-arm64-v8a.apk => Wholphin-arm64-v8a.apk
|
||||
short_name="$(echo $line | sed -E 's/-release-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+-g[a-fA-F0-9]+-[0-9]+//')"
|
||||
# Wholphin-default-release-0.2.9-62-g5c12e03-16-arm64-v8a.apk => Wholphin-arm64-v8a.apk
|
||||
short_name="$(echo $line | sed -E 's/-default-release-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+-g[a-fA-F0-9]+-[0-9]+//')"
|
||||
echo "$line => $short_name"
|
||||
cp "$line" "$short_name"
|
||||
done
|
||||
apks=$(find app/build/outputs/apk -name '*.apk' -print0 | tr '\0' ',' | sed 's/,$//')
|
||||
echo "apks=$apks" >> "$GITHUB_OUTPUT"
|
||||
|
||||
aab=$(find app/build/outputs -name '*.aab')
|
||||
echo "aab=$aab" >> "$GITHUB_OUTPUT"
|
||||
- name: Checksums
|
||||
run: |
|
||||
echo "SHA256 checksums:"
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ android {
|
|||
versionCode = gitTags.trim().lines().size
|
||||
versionName = gitDescribe.trim().removePrefix("v").ifBlank { "0.0.0" }
|
||||
testInstrumentationRunner = "com.github.damontecres.wholphin.test.WholphinTestRunner"
|
||||
|
||||
buildConfigField("long", "BUILD_TIME", System.currentTimeMillis().toString())
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
|
@ -108,6 +110,20 @@ android {
|
|||
}
|
||||
}
|
||||
}
|
||||
flavorDimensions += "version"
|
||||
productFlavors {
|
||||
create("default") {
|
||||
dimension = "version"
|
||||
isDefault = true
|
||||
manifestPlaceholders += mapOf("leanback" to false)
|
||||
buildConfigField("boolean", "UPDATING_ENABLED", "true")
|
||||
}
|
||||
create("appstore") {
|
||||
dimension = "version"
|
||||
manifestPlaceholders += mapOf("leanback" to true)
|
||||
buildConfigField("boolean", "UPDATING_ENABLED", "false")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
|
|
|
|||
7
app/src/appstore/AndroidManifest.xml
Normal file
7
app/src/appstore/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
|
||||
|
||||
</manifest>
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Required for Android 11+ to query voice recognition availability -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.speech.action.RECOGNIZE_SPEECH" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Wholphin"
|
||||
android:name=".WholphinApplication"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout|smallestScreenSize">
|
||||
<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>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".test.TestActivity"
|
||||
android:exported="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>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- <service-->
|
||||
<!-- android:name=".WholphinDreamService"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:label="@string/app_name"-->
|
||||
<!-- android:permission="android.permission.BIND_DREAM_SERVICE">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.service.dreams.DreamService" />-->
|
||||
<!-- <category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </service>-->
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
android:required="${leanback}" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class UpdateChecker
|
|||
|
||||
private val NOTE_REGEX = Regex("<!-- app-note:(.+) -->")
|
||||
|
||||
val ACTIVE = true
|
||||
const val ACTIVE = BuildConfig.UPDATING_ENABLED
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import org.jellyfin.sdk.model.DeviceInfo
|
|||
import timber.log.Timber
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -75,6 +76,34 @@ class DebugViewModel
|
|||
display.supportedModes.orEmpty()
|
||||
}
|
||||
|
||||
val av1Included by lazy {
|
||||
try {
|
||||
Class.forName("androidx.media3.decoder.av1.Libdav1dVideoRenderer")
|
||||
true
|
||||
} catch (_: ClassNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val ffmpegIncluded by lazy {
|
||||
try {
|
||||
Class.forName("androidx.media3.decoder.ffmpeg.FfmpegAudioRenderer")
|
||||
true
|
||||
} catch (_: ClassNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val libMpvLoaded by lazy {
|
||||
try {
|
||||
System.loadLibrary("player")
|
||||
System.loadLibrary("mpv")
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser.value?.rowId?.let {
|
||||
|
|
@ -218,6 +247,7 @@ fun DebugPage(
|
|||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
val buildTime = Date(BuildConfig.BUILD_TIME)
|
||||
val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val installInfo =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
|
@ -239,6 +269,11 @@ fun DebugPage(
|
|||
"Version Code: ${pkgInfo.versionCodeLong}",
|
||||
"ClientInfo: ${viewModel.clientInfo}",
|
||||
"Build type: ${BuildConfig.BUILD_TYPE}",
|
||||
"Build flavor: ${BuildConfig.FLAVOR}",
|
||||
"Build time: $buildTime",
|
||||
"FFMPEG included: ${viewModel.ffmpegIncluded}",
|
||||
"AV1 included: ${viewModel.av1Included}",
|
||||
"libmpv loaded: ${viewModel.libMpvLoaded}",
|
||||
"Debug enabled: ${BuildConfig.DEBUG}",
|
||||
"ABIs: ${Build.SUPPORTED_ABIS.toList()}",
|
||||
) + installInfo
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
# This patches Wholphin for releasing on app stores where self updating is not permitted
|
||||
|
||||
diff --git i/app/src/main/AndroidManifest.xml w/app/src/main/AndroidManifest.xml
|
||||
index 4479ae86..80596698 100644
|
||||
--- i/app/src/main/AndroidManifest.xml
|
||||
+++ w/app/src/main/AndroidManifest.xml
|
||||
@@ -6,7 +6,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
- <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
@@ -20,7 +19,7 @@
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
- android:required="false" />
|
||||
+ android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
diff --git i/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt w/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt
|
||||
index 9e0665dd..bc1e1c16 100644
|
||||
--- i/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt
|
||||
+++ w/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt
|
||||
@@ -65,7 +65,7 @@ class UpdateChecker
|
||||
|
||||
private val NOTE_REGEX = Regex("<!-- app-note:(.+) -->")
|
||||
|
||||
- val ACTIVE = true
|
||||
+ val ACTIVE = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.github.damontecres.wholphin.ui
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.ActivityInfo
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
|
|
@ -14,6 +16,7 @@ import androidx.compose.ui.test.performTextInput
|
|||
import androidx.compose.ui.test.pressKey
|
||||
import androidx.compose.ui.test.requestFocus
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.github.damontecres.wholphin.services.ScreensaverService
|
||||
import com.github.damontecres.wholphin.services.ScreensaverState
|
||||
import com.github.damontecres.wholphin.services.SetupDestination
|
||||
|
|
@ -49,8 +52,11 @@ import org.junit.Assert
|
|||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -61,7 +67,23 @@ class BasicUiTests {
|
|||
@get:Rule(order = 0)
|
||||
var hiltRule = HiltAndroidRule(this)
|
||||
|
||||
// From https://github.com/robolectric/robolectric/pull/4736#issuecomment-1831034882
|
||||
@get:Rule(order = 1)
|
||||
val addActivityToRobolectricRule =
|
||||
object : TestWatcher() {
|
||||
override fun starting(description: Description?) {
|
||||
super.starting(description)
|
||||
val appContext: Application = ApplicationProvider.getApplicationContext()
|
||||
val activityInfo =
|
||||
ActivityInfo().apply {
|
||||
name = TestActivity::class.java.name
|
||||
packageName = appContext.packageName
|
||||
}
|
||||
shadowOf(appContext.packageManager).addOrUpdateActivity(activityInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@get:Rule(order = 2)
|
||||
val composeTestRule = createAndroidComposeRule<TestActivity>()
|
||||
|
||||
@Inject
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue