diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bb8628ea..277807be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,8 +15,6 @@ concurrency: cancel-in-progress: true env: - APK_SHORT_NAME: Wholphin-release.apk - APK_DEBUG_SHORT_NAME: Wholphin-debug.apk BRANCH_NAME: ${{ github.ref_name }} jobs: @@ -58,23 +56,26 @@ jobs: SIGNING_KEY: "${{ secrets.SIGNING_KEY }}" run: | ./gradlew clean assembleRelease assembleDebug - echo "apk=$(ls app/build/outputs/apk/release/Wholphin-release*.apk)" >> "$GITHUB_OUTPUT" - echo "apk_debug=$(ls app/build/outputs/apk/debug/Wholphin-debug*.apk)" >> "$GITHUB_OUTPUT" - - name: Verify signature + echo "apks=$(find app/build/outputs/apk -name '*.apk' | sort)" >> "$GITHUB_OUTPUT" + - name: Verify signatures run: | - echo "Verify release APK" - ${{env.ANDROID_SDK_ROOT}}/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner verify --verbose --print-certs "${{ steps.build.outputs.apk }}" - echo "Verify debug APK" - ${{env.ANDROID_SDK_ROOT}}/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner verify --verbose --print-certs "${{ steps.build.outputs.apk_debug }}" - - name: Copy APK to ${{ env.APK_SHORT_NAME }} + echo "Verify APK signatures" + while IFS= read -r line; do + ${{env.ANDROID_SDK_ROOT}}/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner verify --verbose --print-certs "$line" + done <<< "${{ steps.build.outputs.apks }}" + - name: Copy APK to shorter names + id: apks run: | - cp "${{ steps.build.outputs.apk }}" "${{ env.APK_SHORT_NAME }}" - cp "${{ steps.build.outputs.apk_debug }}" "${{ env.APK_DEBUG_SHORT_NAME }}" + while IFS= read -r line; do + # Wholphin-debug-0.2.9-62-g5c12e03-16-arm64-v8a.apk => Wholphin-debug-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]+//')" + echo "$line => $short_name" + cp "$line" "$short_name" + done <<< "${{ steps.build.outputs.apks }}" - name: Checksums run: | echo "SHA256 checksums:" - sha256sum "${{ steps.build.outputs.apk }}" "${{ env.APK_SHORT_NAME }}" - sha256sum "${{ steps.build.outputs.apk_debug }}" "${{ env.APK_DEBUG_SHORT_NAME }}" + find app/build/outputs/apk -name '*.apk' | xargs sha256sum - name: Delete ${{ env.TAG_NAME }} tag and release uses: dev-drprasad/delete-tag-and-release@v1.1 with: @@ -99,7 +100,7 @@ jobs: with: allowUpdates: true artifactErrorsFailBuild: true - artifacts: "${{ steps.build.outputs.apk_debug }},${{ steps.build.outputs.apk }},${{ env.APK_DEBUG_SHORT_NAME }},${{ env.APK_SHORT_NAME }}" + artifacts: "${{ steps.apks.outputs.apks }}" draft: false generateReleaseNotes: false makeLatest: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b6a89bb..92967b24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,21 +37,31 @@ jobs: SIGNING_KEY: "${{ secrets.SIGNING_KEY }}" run: | ./gradlew clean assembleRelease - echo "apk=$(ls app/build/outputs/apk/release/Wholphin-release*.apk)" >> "$GITHUB_OUTPUT" - - name: Verify signature + echo "apks=$(find app/build/outputs/apk -name '*.apk' | sort)" >> "$GITHUB_OUTPUT" + - name: Verify signatures run: | - ${{env.ANDROID_SDK_ROOT}}/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner verify --verbose --print-certs "${{ steps.buildapp.outputs.apk }}" + echo "Verify APK signatures" + while IFS= read -r line; do + ${{env.ANDROID_SDK_ROOT}}/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner verify --verbose --print-certs "$line" + done <<< "${{ steps.build.outputs.apks }}" + - name: Copy APK to ${{ env.APK_SHORT_NAME }} + id: apks run: | - cp "${{ steps.buildapp.outputs.apk }}" "${{ env.APK_SHORT_NAME }}" + while IFS= read -r line; do + short_name="$(echo $line | sed -E 's/-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 <<< "${{ steps.build.outputs.apks }}" + echo "apks=$(find app/build/outputs/apk -name '*.apk' -print0 | tr '\0' ',' | sed 's/,$//')" >> "$GITHUB_OUTPUT" - name: Checksums run: | echo "SHA256 checksums:" - sha256sum "${{ steps.buildapp.outputs.apk }}" "${{ env.APK_SHORT_NAME }}" + find app/build/outputs/apk -name '*.apk' | xargs sha256sum - name: Create GitHub release uses: ncipollo/release-action@v1 with: - artifacts: "${{ steps.buildapp.outputs.apk }},${{ env.APK_SHORT_NAME }}" + artifacts: "${{ steps.apks.outputs.apks }}" makeLatest: true prerelease: false generateReleaseNotes: false diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 486a31fd..cc21d64a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -139,8 +139,10 @@ android { variant.outputs .map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl } .forEach { output -> + val abi = output.getFilter("ABI").let { if (it != null) "-$it" else "" } val outputFileName = - "Wholphin-${variant.baseName}-${variant.versionName}-${variant.versionCode}.apk" + "Wholphin-${variant.baseName}-${variant.versionName}-${variant.versionCode}$abi.apk" + println(outputFileName) output.outputFileName = outputFileName } } @@ -148,7 +150,7 @@ android { splits { abi { - isEnable = false + isEnable = true reset() include("armeabi-v7a", "arm64-v8a") isUniversalApk = true diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt b/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt index 4b6b2b42..5deb18cc 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt @@ -24,6 +24,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject @@ -51,9 +53,8 @@ class UpdateChecker ) { companion object { // TODO apk names - private const val ASSET_NAME = "Wholphin.apk" - private const val DEBUG_ASSET_NAME = "Wholphin-debug.apk" - private const val RELEASE_ASSET_NAME = "Wholphin-release.apk" + private const val ASSET_NAME = "Wholphin" + private const val APK_NAME = "$ASSET_NAME.apk" private const val APK_MIME_TYPE = "application/vnd.android.package-archive" @@ -116,15 +117,10 @@ class UpdateChecker suspend fun getLatestRelease(updateUrl: String): Release? { return withContext(Dispatchers.IO) { - val preferredAsset = - if (PreferenceManager - .getDefaultSharedPreferences(context) - .getBoolean("updatePreferRelease", true) - ) { - RELEASE_ASSET_NAME - } else { - DEBUG_ASSET_NAME - } + val preferRelease = + PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean("updatePreferRelease", true) val request = Request @@ -136,21 +132,15 @@ class UpdateChecker if (it.isSuccessful && it.body != null) { val result = Json.parseToJsonElement(it.body!!.string()) val name = result.jsonObject["name"]?.jsonPrimitive?.contentOrNull - val version = Version.Companion.tryFromString(name) + val version = Version.tryFromString(name) val publishedAt = result.jsonObject["published_at"]?.jsonPrimitive?.contentOrNull val body = result.jsonObject["body"]?.jsonPrimitive?.contentOrNull val downloadUrl = result.jsonObject["assets"] ?.jsonArray - ?.firstOrNull { asset -> - val assetName = - asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull - assetName == ASSET_NAME || assetName == preferredAsset - }?.jsonObject - ?.get("browser_download_url") - ?.jsonPrimitive - ?.contentOrNull + ?.let { assets -> getDownloadUrl(assets, preferRelease) } + Timber.v("version=$version, downloadUrl=$downloadUrl") if (version != null) { val notes = if (body.isNotNullOrBlank()) { @@ -174,6 +164,35 @@ class UpdateChecker } } + private fun getDownloadUrl( + assets: JsonArray, + preferRelease: Boolean, + ): String? { + val abiSuffix = Build.SUPPORTED_ABIS.firstOrNull().let { if (it != null) "-$it" else "" } + val releaseSuffix = if (preferRelease) "-release" else "-debug" + val preferredNames = + listOf( + "$ASSET_NAME${releaseSuffix}$abiSuffix.apk", + "$ASSET_NAME$releaseSuffix.apk", + "$ASSET_NAME.apk", + ) + var preferredAsset: JsonObject? = null + outer@ for (name in preferredNames) { + for (asset in assets) { + val assetName = + asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull + if (name == assetName) { + preferredAsset = asset.jsonObject + break@outer + } + } + } + return preferredAsset + ?.get("browser_download_url") + ?.jsonPrimitive + ?.contentOrNull + } + suspend fun installRelease( release: Release, callback: DownloadCallback, @@ -195,7 +214,7 @@ class UpdateChecker if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, ASSET_NAME) + put(MediaStore.MediaColumns.DISPLAY_NAME, APK_NAME) put(MediaStore.MediaColumns.MIME_TYPE, APK_MIME_TYPE) put( MediaStore.MediaColumns.RELATIVE_PATH, @@ -267,7 +286,7 @@ class UpdateChecker val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) downloadDir.mkdirs() - val targetFile = File(downloadDir, ASSET_NAME) + val targetFile = File(downloadDir, APK_NAME) targetFile.outputStream().use { output -> response.body!!.byteStream().use { input -> copyTo(input, output, callback = callback) @@ -320,7 +339,7 @@ class UpdateChecker } else { val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - val targetFile = File(downloadDir, ASSET_NAME) + val targetFile = File(downloadDir, APK_NAME) if (targetFile.exists()) { targetFile.delete() } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt index 8535c336..6a4d973a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt @@ -1,6 +1,7 @@ package com.github.damontecres.wholphin.ui.detail import android.content.Context +import android.os.Build import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.focusable @@ -224,6 +225,11 @@ fun DebugPage( style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface, ) + Text( + text = "ABIs: ${Build.SUPPORTED_ABIS.toList()}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface, + ) } } item { @@ -237,12 +243,12 @@ fun DebugPage( color = MaterialTheme.colorScheme.onSurface, ) Text( - text = "Current server: ${viewModel.serverRepository.currentServer}", + text = "Current server: ${viewModel.serverRepository.currentServer.value}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface, ) Text( - text = "Current user: ${viewModel.serverRepository.currentUser}", + text = "Current user: ${viewModel.serverRepository.currentUser.value}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface, )