feat(Settings): allow directly exporting patched apk

This commit is contained in:
rushiiMachine 2025-06-23 23:36:11 -07:00
parent 172b53c5f6
commit e83374253c
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
8 changed files with 62 additions and 44 deletions

17
INFO.md
View file

@ -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.

View file

@ -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.

View file

@ -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"
}
}

View file

@ -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

View file

@ -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 = """

View file

@ -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(

View file

@ -53,9 +53,9 @@
<string name="setting_developer_options">Developer options</string>
<string name="setting_developer_options_desc">Don\'t enable this unless you know what it does! (yes, really)</string>
<string name="setting_keep_patched_apks">Keep patched APKs</string>
<string name="setting_keep_patched_apks_desc">Keep all patched files and APKs after installing for debugging. This setting may drastically slow down (20x) patching on certain devices!</string>
<string name="setting_keep_patched_apks_desc">Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session.</string>
<string name="settings_clear_cache">Clear cache</string>
<string name="settings_see_patched_apks">See patched APKs</string>
<string name="settings_export_apk">Export APK</string>
<string name="theme_system">System</string>
<string name="theme_dark">Dark</string>
@ -205,6 +205,7 @@
<string name="log_section_error">Error Stacktrace</string>
<string name="log_section_log">Installation Log</string>
<string name="log_action_export">Export log</string>
<string name="log_action_export_apk">Export APK</string>
<string name="log_action_share">Share log</string>
<string name="logs_title">Logs</string>

View file

@ -3,4 +3,7 @@
<cache-path
name="cache"
path="." />
<files-path
name="files"
path="." />
</paths>