From a1e7594698c7ce1f1d6e34bd036ad6fa1ed9f138 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:47:28 -0800 Subject: [PATCH] feat: always use Ktor downloader Android's DownloadManager has too many issues at this point, it's become unbearable. SecurityException here, random failure there, horrible logging, etc. It's much more reliable to directly use an HTTP client to download our stuff. Maybe I'll reimplement the download progress notification separately. Also turns out you can't use Android's DownloadManager to download to the app's internal data directory. very funny. --- .../aliucord/manager/ManagerApplication.kt | 1 - .../manager/di/DownloadManagerProvider.kt | 40 ------------- .../aliucord/manager/manager/PathManager.kt | 22 ++++---- .../manager/manager/PreferencesManager.kt | 3 - .../download/AndroidDownloadManager.kt | 3 +- .../manager/download/KtorDownloadManager.kt | 5 +- .../patcher/steps/base/DownloadStep.kt | 39 +------------ .../dialogs/AlternativeDownloaderDialog.kt | 56 ------------------- .../ui/screens/settings/SettingsScreen.kt | 15 ----- .../ui/widgets/updater/UpdaterViewModel.kt | 20 +++---- app/src/main/res/values/strings.xml | 5 -- 11 files changed, 29 insertions(+), 180 deletions(-) delete mode 100644 app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/AlternativeDownloaderDialog.kt diff --git a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt index 9b8a829..d32a7c5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt @@ -70,7 +70,6 @@ class ManagerApplication : Application() { singleOf(::InstallerManager) singleOf(::OverlayManager) - singleOf(::DownloadManagerProvider) singleOf(::AndroidDownloadManager) singleOf(::KtorDownloadManager) }) diff --git a/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt b/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt deleted file mode 100644 index 21c6efe..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.aliucord.manager.di - -import com.aliucord.manager.manager.PreferencesManager -import com.aliucord.manager.manager.download.* -import com.aliucord.manager.patcher.util.Signer.getKoin -import com.aliucord.manager.util.IS_PROBABLY_EMULATOR -import org.koin.core.annotation.KoinInternalApi -import kotlin.reflect.KClass - -/** - * Handle providing the correct install manager based on preferences and device type. - */ -class DownloadManagerProvider(private val prefs: PreferencesManager) { - fun getActiveDownloader(): IDownloadManager = - getDownloader(prefs.downloader) - - @OptIn(KoinInternalApi::class) - fun getDownloader(type: DownloaderSetting): IDownloadManager = - getKoin().scopeRegistry.rootScope.get(clazz = type.downloaderClass) - - companion object { - fun getDefaultDownloader(): DownloaderSetting { - // Ktor downloader has specific fix for emulator - return if (IS_PROBABLY_EMULATOR) { - DownloaderSetting.Ktor - } else { - DownloaderSetting.Android - } - } - } -} - -enum class DownloaderSetting( - // @StringRes - // private val localizedName: Int, - val downloaderClass: KClass, -) { - Android(AndroidDownloadManager::class), - Ktor(KtorDownloadManager::class), -} 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 498cb8e..19a3a62 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt @@ -37,7 +37,7 @@ class PathManager( * Use the external cache directory (`/storage/emulated/0/Android/data/com.aliucord.manager/cache`) * when dev mode or preserving APKs is enabled. Otherwise, default to internal app cache. */ - private val externalCacheDir + private val cacheDir get() = when (prefs.devMode || prefs.keepPatchedApks) { false -> context.cacheDir true -> context.externalCacheDir @@ -48,16 +48,16 @@ class PathManager( * Delete the entire cache dir and recreate it. */ fun clearCache() { - if (!externalCacheDir.deleteRecursively()) + if (!cacheDir.deleteRecursively()) throw IllegalStateException("Failed to delete cache") - externalCacheDir.mkdirs() + cacheDir.mkdirs() } /** * Create a new subfolder in the Discord APK cache for a specific version. */ - fun discordApkVersionCache(version: Int): File = externalCacheDir + fun discordApkVersionCache(version: Int): File = cacheDir .resolve("discord") .resolve(version.toString()) .apply { mkdirs() } @@ -65,45 +65,45 @@ class PathManager( /** * Resolve a specific path for a cached injector. */ - fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = externalCacheDir + fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = cacheDir .resolve("injector").apply { mkdirs() } .resolve("$version${if (custom) ".custom" else ""}.dex") /** * Get all the versions of custom injector builds. */ - fun customInjectorDexs() = listCustomFiles(externalCacheDir.resolve("injector")) + fun customInjectorDexs() = listCustomFiles(cacheDir.resolve("injector")) /** * Resolve a specific path for a versioned cached Aliuhook build */ - fun cachedAliuhookAAR(version: SemVer) = externalCacheDir + fun cachedAliuhookAAR(version: SemVer) = cacheDir .resolve("aliuhook").apply { mkdirs() } .resolve("$version.aar") /** * Resolve a specific path for a versioned smali patches archive. */ - fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = externalCacheDir + fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = cacheDir .resolve("patches").apply { mkdirs() } .resolve("$version${if (custom) ".custom" else ""}.zip") /** * Get all the versions of custom smali bundles. */ - fun customSmaliPatches() = listCustomFiles(externalCacheDir.resolve("patches")) + fun customSmaliPatches() = listCustomFiles(cacheDir.resolve("patches")) /** * Singular Kotlin file of the most up-to-date version * since the stdlib is backwards compatible. */ - fun cachedKotlinDex() = externalCacheDir + fun cachedKotlinDex() = cacheDir .resolve("kotlin.dex") /** * The temporary working directory of a currently executing patching process. */ - fun patchingWorkingDir() = externalCacheDir + fun patchingWorkingDir() = cacheDir .resolve("patched") private companion object { diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt index 639b781..ba70699 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt @@ -1,8 +1,6 @@ package com.aliucord.manager.manager import android.content.SharedPreferences -import com.aliucord.manager.di.DownloadManagerProvider -import com.aliucord.manager.di.DownloaderSetting import com.aliucord.manager.manager.base.BasePreferenceManager import com.aliucord.manager.ui.components.Theme @@ -10,7 +8,6 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager var theme by enumPreference("theme", Theme.DARK) var dynamicColor by booleanPreference("dynamic_color", false) var devMode by booleanPreference("dev_mode", false) - var downloader by enumPreference("downloader", DownloadManagerProvider.getDefaultDownloader()) var installer by enumPreference("installer", InstallerSetting.PM) var keepPatchedApks by booleanPreference("keep_patched_apks", false) } diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/download/AndroidDownloadManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/download/AndroidDownloadManager.kt index 6ea6726..94fb495 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/download/AndroidDownloadManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/download/AndroidDownloadManager.kt @@ -6,6 +6,7 @@ import android.content.Context import android.database.Cursor import android.net.Uri import androidx.core.content.getSystemService +import androidx.core.net.toUri import com.aliucord.manager.BuildConfig import com.aliucord.manager.R import com.aliucord.manager.manager.download.IDownloadManager.ProgressListener @@ -34,7 +35,7 @@ class AndroidDownloadManager(application: Application) : IDownloadManager { out.parentFile?.mkdirs() // Create and start a download in the system DownloadManager - val downloadId = DownloadManager.Request(Uri.parse(url)) + val downloadId = DownloadManager.Request(url.toUri()) .setTitle("Aliucord Manager") .setDescription("Downloading ${out.name}...") .setDestinationUri(Uri.fromFile(out)) diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/download/KtorDownloadManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/download/KtorDownloadManager.kt index 8d9824d..8bde805 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/download/KtorDownloadManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/download/KtorDownloadManager.kt @@ -1,6 +1,7 @@ package com.aliucord.manager.manager.download import com.aliucord.manager.manager.download.IDownloadManager.Result +import com.aliucord.manager.util.IS_PROBABLY_EMULATOR import io.ktor.client.HttpClient import io.ktor.client.request.header import io.ktor.client.request.prepareGet @@ -28,7 +29,9 @@ class KtorDownloadManager(private val http: HttpClient) : IDownloadManager { val httpStmt = http.prepareGet(url) { // Disable compression due to bug on emulators // This header cannot be set with Android's DownloadManager - header(HttpHeaders.AcceptEncoding, null) + if (IS_PROBABLY_EMULATOR) { + header(HttpHeaders.AcceptEncoding, null) + } } httpStmt.execute { resp -> diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/base/DownloadStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/base/DownloadStep.kt index 8922acd..c0225e4 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/base/DownloadStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/base/DownloadStep.kt @@ -1,22 +1,14 @@ package com.aliucord.manager.patcher.steps.base -import android.app.DownloadManager import android.content.Context -import android.util.Log import android.widget.Toast import androidx.annotation.CallSuper import androidx.compose.runtime.Stable -import com.aliucord.manager.BuildConfig import com.aliucord.manager.R -import com.aliucord.manager.di.DownloadManagerProvider -import com.aliucord.manager.di.DownloaderSetting -import com.aliucord.manager.manager.OverlayManager -import com.aliucord.manager.manager.PreferencesManager -import com.aliucord.manager.manager.download.AndroidDownloadManager import com.aliucord.manager.manager.download.IDownloadManager +import com.aliucord.manager.manager.download.KtorDownloadManager import com.aliucord.manager.patcher.StepRunner import com.aliucord.manager.patcher.steps.StepGroup -import com.aliucord.manager.ui.components.dialogs.AlternativeDownloaderDialog import com.aliucord.manager.util.showToast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -27,9 +19,7 @@ import java.io.File @Stable abstract class DownloadStep : Step(), KoinComponent { private val context: Context by inject() - private val overlays: OverlayManager by inject() - private val prefs: PreferencesManager by inject() - private val downloaders: DownloadManagerProvider by inject() + private val downloader: KtorDownloadManager by inject() /** * The remote url to download @@ -67,7 +57,7 @@ abstract class DownloadStep : Step(), KoinComponent { targetFile.delete() } - val result = downloaders.getActiveDownloader().download(targetUrl, targetFile) { newProgress -> + val result = downloader.download(targetUrl, targetFile) { newProgress -> progress = newProgress ?: -1f } @@ -96,29 +86,6 @@ abstract class DownloadStep : Step(), KoinComponent { Toast.makeText(context, toastText, Toast.LENGTH_LONG).show() } - // If this is a specific Android DownloadManager error, then prompt to use a different downloader - if (result is AndroidDownloadManager.Error - && result.reason in arrayOf(DownloadManager.ERROR_HTTP_DATA_ERROR, DownloadManager.ERROR_CANNOT_RESUME) - && prefs.downloader != DownloaderSetting.Ktor - ) { - state = StepState.Error - - val confirmed = overlays.startComposableForResult { callback -> - AlternativeDownloaderDialog( - onConfirm = { callback(true) }, - onDismiss = { callback(false) }, - ) - } - - if (confirmed) { - Log.i(BuildConfig.TAG, "Changing to alternative downloader after failure") - prefs.downloader = DownloaderSetting.Ktor - state = StepState.Running - - return execute(container) - } - } - throw Error("Failed to download: $result") } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/AlternativeDownloaderDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/AlternativeDownloaderDialog.kt deleted file mode 100644 index eee0a8c..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/dialogs/AlternativeDownloaderDialog.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.aliucord.manager.ui.components.dialogs - -import androidx.compose.foundation.layout.size -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.aliucord.manager.R - -@Composable -fun AlternativeDownloaderDialog( - onConfirm: () -> Unit, - onDismiss: () -> Unit, -) { - AlertDialog( - onDismissRequest = onDismiss, - confirmButton = { - FilledTonalButton( - onClick = onConfirm, - colors = ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary, - ), - ) { - Text(stringResource(R.string.action_retry)) - } - }, - dismissButton = { - TextButton( - onClick = onDismiss, - colors = ButtonDefaults.textButtonColors( - contentColor = MaterialTheme.colorScheme.error, - ), - ) { - Text(stringResource(R.string.action_cancel)) - } - }, - title = { Text(stringResource(R.string.alt_downloader_dialog_title)) }, - text = { - Text( - text = stringResource(R.string.alt_downloader_dialog_desc), - textAlign = TextAlign.Center, - ) - }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_download), - contentDescription = null, - modifier = Modifier.size(32.dp), - ) - }, - ) -} 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 6d93cee..9ba153c 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 @@ -20,7 +20,6 @@ import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.getScreenModel import com.aliucord.manager.R -import com.aliucord.manager.di.DownloaderSetting import com.aliucord.manager.ui.components.BackButton import com.aliucord.manager.ui.components.settings.* import com.aliucord.manager.ui.screens.settings.components.ThemeDialog @@ -102,20 +101,6 @@ class SettingsScreen : Screen, Parcelable { onPrefChange = { preferences.keepPatchedApks = it }, ) - SettingsSwitch( - label = stringResource(R.string.setting_alt_downloader), - secondaryLabel = stringResource(R.string.setting_alt_downloader_desc), - icon = { Icon(painterResource(R.drawable.ic_download), null) }, - pref = preferences.downloader == DownloaderSetting.Ktor, - onPrefChange = { - preferences.downloader = if (it) { - DownloaderSetting.Ktor - } else { - DownloaderSetting.Android - } - } - ) - var clearedCache by rememberSaveable { mutableStateOf(false) } Button( shape = ShapeDefaults.Large, diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt index 9313155..fd379c6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/widgets/updater/UpdaterViewModel.kt @@ -5,10 +5,10 @@ import androidx.compose.runtime.* import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aliucord.manager.BuildConfig -import com.aliucord.manager.di.DownloadManagerProvider import com.aliucord.manager.domain.repository.GithubRepository import com.aliucord.manager.manager.InstallerManager import com.aliucord.manager.manager.InstallerSetting +import com.aliucord.manager.manager.download.KtorDownloadManager import com.aliucord.manager.network.utils.SemVer import com.aliucord.manager.network.utils.getOrNull import kotlinx.coroutines.Dispatchers @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch class UpdaterViewModel( private val github: GithubRepository, - private val downloaders: DownloadManagerProvider, + private val downloader: KtorDownloadManager, private val installers: InstallerManager, private val application: Application, ) : ViewModel() { @@ -40,17 +40,15 @@ class UpdaterViewModel( viewModelScope.launch { isWorking = true - application.externalCacheDir!!.resolve("manager.apk").also { - it.delete() + val apkFile = application.cacheDir.resolve("manager.apk").apply { delete() } - downloaders.getActiveDownloader().download(url, it) - installers.getInstaller(InstallerSetting.PM).install( - apks = listOf(it), - silent = true, - ) + downloader.download(url, apkFile) + installers.getInstaller(InstallerSetting.PM).install( + apks = listOf(apkFile), + silent = true, + ) - it.delete() - } + apkFile.delete() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a9708b5..68d0973 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,8 +49,6 @@ Enables Material You theming on Android 12+ Keep patched APKs Keep all patched files and APKs after installing for debugging. - Alternative downloader - Use a custom HTTP client instead of the system DownloadManager. May fix download errors on specific devices. System Dark @@ -171,9 +169,6 @@ Continue Open Play Protect - Alternative Downloader - This download failed with a specific error that may indicate issues with the system\'s DownloadManager. Would you like to retry with the the alternative downloader implementation? - Fun Fact: %s Did you know that Google Play Protect is useless? Did you know Aliucord with 150+ plugins somehow runs better than Discord\'s new app?