From e83374253c89a4d65b0bef78cd3cc477bfced2fb Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:36:11 -0700 Subject: [PATCH] feat(Settings): allow directly exporting patched apk --- INFO.md | 17 --------- .../aliucord/manager/manager/PathManager.kt | 5 +++ .../network/services/AliucordGithubService.kt | 1 - .../steps/download/CopyDependenciesStep.kt | 3 +- .../ui/screens/settings/SettingsModel.kt | 36 +++++++++++++++++++ .../ui/screens/settings/SettingsScreen.kt | 36 ++++++++----------- app/src/main/res/values/strings.xml | 5 +-- app/src/main/res/xml/provider_paths.xml | 3 ++ 8 files changed, 62 insertions(+), 44 deletions(-) diff --git a/INFO.md b/INFO.md index f72ecbd..d08f190 100644 --- a/INFO.md +++ b/INFO.md @@ -1,20 +1,3 @@ -## Obtaining Patched APKs - -Unfortunately, starting with Android 10, file managers cannot access external data of other apps without root. -You will have to manually download the patched APK via `adb`. - -ADB command (assuming the first user profile): - -```shell -$ adb pull /storage/emulated/0/Android/data/com.aliucord.manager/cache/patched/patched.apk -``` - -This file should only be used after the patching process has successfully completed. -If the process failed at any step before "Installing", then the APK will be unusable. - -The patched APK **should not be reused on another device**. Refer to the section [below](#backing-up-aliucord) -for more information. - ## Backing up Aliucord Note: This section is about the app installation itself, not the installed plugins, themes, and settings. diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt index f8e49c0..74640f6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt @@ -101,6 +101,11 @@ class PathManager( fun patchingWorkingDir() = patchingDir .resolve("patched") + /** + * The APK that is worked on during the patching process. + */ + fun patchedApk() = patchingWorkingDir().resolve("patched.apk") + private companion object { /** * List all the files that follow the ```[SemVer].custom.*``` naming scheme. diff --git a/app/src/main/kotlin/com/aliucord/manager/network/services/AliucordGithubService.kt b/app/src/main/kotlin/com/aliucord/manager/network/services/AliucordGithubService.kt index 10e6782..526b785 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/services/AliucordGithubService.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/services/AliucordGithubService.kt @@ -38,6 +38,5 @@ class AliucordGithubService( const val MANAGER_REPO = "Manager" const val DATA_JSON_URL = "https://raw.githubusercontent.com/$ORG/$MAIN_REPO/builds/data.json" - const val PATCHED_APKS_INFO_URL = "https://github.com/$ORG/$MANAGER_REPO/blob/main/INFO.md#obtaining-patched-apks" } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt index 50523f2..7ef16c4 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt @@ -18,8 +18,7 @@ class CopyDependenciesStep : Step(), KoinComponent { /** * The target APK file which can be modified during patching */ - val patchedApk: File = paths.patchingWorkingDir() - .resolve("patched.apk") + val patchedApk: File = paths.patchedApk() override val group = StepGroup.Download override val localizedName = R.string.patch_step_copy_deps diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt index d01a563..c0916f5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsModel.kt @@ -1,7 +1,10 @@ package com.aliucord.manager.ui.screens.settings import android.app.Application +import android.content.Intent +import android.util.Log import androidx.compose.runtime.* +import androidx.core.content.FileProvider import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import com.aliucord.manager.BuildConfig @@ -18,6 +21,9 @@ class SettingsModel( ) : ScreenModel { val installInfo = InstallInfo + var patchedApkExists by mutableStateOf(paths.patchedApk().exists()) + private set + var showThemeDialog by mutableStateOf(false) private set @@ -39,6 +45,7 @@ class SettingsModel( fun clearCache() = screenModelScope.launchBlock { paths.clearCache() + patchedApkExists = false application.showToast(R.string.action_cleared_cache) } @@ -47,6 +54,35 @@ class SettingsModel( application.showToast(R.string.action_copied) } + fun shareApk() = screenModelScope.launchBlock { + val file = paths.patchingWorkingDir().resolve("patched.apk") + val fileUri = FileProvider.getUriForFile( + /* context = */ application, + /* authority = */ "${BuildConfig.APPLICATION_ID}.provider", + /* file = */ file, + /* displayName = */ "Aliucord.apk", + ) + + val intent = Intent(Intent.ACTION_SEND) + .setType("application/vnd.android.package-archive") + .putExtra(Intent.EXTRA_STREAM, fileUri) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .let { + Intent.createChooser( + /* target = */ it, + /* title = */ application.getString(R.string.log_action_export_apk), + ) + } + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + try { + application.startActivity(intent) + } catch (t: Throwable) { + Log.w(BuildConfig.TAG, "Failed to share APK", t) + application.showToast(R.string.status_failed) + } + } + companion object { @Suppress("KotlinConstantConditions") private val InstallInfo: String = """ diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt index 3c1f327..37babc5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/settings/SettingsScreen.kt @@ -15,14 +15,12 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel import com.aliucord.manager.R -import com.aliucord.manager.network.services.AliucordGithubService import com.aliucord.manager.ui.components.BackButton import com.aliucord.manager.ui.components.MainActionButton import com.aliucord.manager.ui.components.settings.* @@ -94,10 +92,7 @@ class SettingsScreen : Screen, Parcelable { secondaryLabel = stringResource(R.string.setting_developer_options_desc), pref = preferences.devMode, icon = { Icon(painterResource(R.drawable.ic_code), null) }, - onPrefChange = { - preferences.devMode = it - clearedCache = false // Cache dir changes with this setting - }, + onPrefChange = { preferences.devMode = it }, ) SettingsSwitch( @@ -105,13 +100,22 @@ class SettingsScreen : Screen, Parcelable { secondaryLabel = stringResource(R.string.setting_keep_patched_apks_desc), icon = { Icon(painterResource(R.drawable.ic_delete_forever), null) }, pref = preferences.keepPatchedApks, - onPrefChange = { - model.setKeepPatchedApks(it) - clearedCache = false // Cache dir changes with this setting - }, + onPrefChange = { model.setKeepPatchedApks(it) }, modifier = Modifier.padding(bottom = 18.dp), ) + if (preferences.keepPatchedApks) { + MainActionButton( + text = stringResource(R.string.log_action_export_apk), + icon = painterResource(R.drawable.ic_save), + enabled = model.patchedApkExists, + onClick = model::shareApk, + modifier = Modifier + .padding(horizontal = 18.dp, vertical = 9.dp) + .fillMaxWidth() + ) + } + MainActionButton( text = stringResource(R.string.settings_clear_cache), icon = painterResource(R.drawable.ic_delete_forever), @@ -128,18 +132,6 @@ class SettingsScreen : Screen, Parcelable { .fillMaxWidth() ) - if (preferences.keepPatchedApks) { - val handler = LocalUriHandler.current - MainActionButton( - text = stringResource(R.string.settings_see_patched_apks), - icon = painterResource(R.drawable.ic_launch), - onClick = { handler.openUri(AliucordGithubService.PATCHED_APKS_INFO_URL) }, - modifier = Modifier - .padding(horizontal = 18.dp, vertical = 9.dp) - .fillMaxWidth() - ) - } - SettingsHeader(stringResource(R.string.settings_header_info)) Row( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31e670e..788bdcb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -53,9 +53,9 @@ Developer options Don\'t enable this unless you know what it does! (yes, really) Keep patched APKs - Keep all patched files and APKs after installing for debugging. This setting may drastically slow down (20x) patching on certain devices! + Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session. Clear cache - See patched APKs + Export APK System Dark @@ -205,6 +205,7 @@ Error Stacktrace Installation Log Export log + Export APK Share log Logs diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index f3830f2..9354caf 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -3,4 +3,7 @@ +