diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 27268bc3..1c38cc89 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,9 +23,10 @@
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- tools:ignore="AllowBackup">
-
+ android:supportsRtl="true">
+
+
+
{
- @Suppress("DEPRECATION") // No.
- val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT)!!
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- startActivity(confirmationIntent)
- }
-
- PackageInstaller.STATUS_SUCCESS -> showToast(R.string.installer_success)
- PackageInstaller.STATUS_FAILURE_ABORTED -> showToast(R.string.installer_aborted)
-
- else -> {
- Log.i(BuildConfig.TAG, "Install failed with error code $statusCode")
-
- if (errorMessages[statusCode] != null) {
- showToast(errorMessages[statusCode]!!)
- } else {
- showToast(R.string.install_error_code, statusCode)
- }
- }
- }
-
- stopSelf()
- return START_NOT_STICKY
- }
-
- override fun onBind(intent: Intent): IBinder? = null
-}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt
index 026b4c9c..190b58ad 100644
--- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt
@@ -1,12 +1,13 @@
package com.aliucord.manager.installer.steps.install
-import android.app.Application
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.steps.base.Step
+import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
-import com.aliucord.manager.installer.util.installApks
+import com.aliucord.manager.installers.InstallerResult
+import com.aliucord.manager.manager.InstallerManager
import com.aliucord.manager.manager.PreferencesManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -15,7 +16,7 @@ import org.koin.core.component.inject
* Install the final APK with the system's PackageManager.
*/
class InstallStep : Step(), KoinComponent {
- private val application: Application by inject()
+ private val installers: InstallerManager by inject()
private val prefs: PreferencesManager by inject()
override val group = StepGroup.Install
@@ -24,9 +25,19 @@ class InstallStep : Step(), KoinComponent {
override suspend fun execute(container: StepRunner) {
val apk = container.getStep().patchedApk
- application.installApks(
+ val result = installers.getActiveInstaller().waitInstall(
+ apks = listOf(apk),
silent = !prefs.devMode,
- apks = arrayOf(apk),
)
+
+ when (result) {
+ is InstallerResult.Error -> throw Error("Failed to install APKs: ${result.debugReason}")
+ is InstallerResult.Cancelled -> {
+ // The install screen is automatically closed immediately once cleanup finishes
+ state = StepState.Skipped
+ }
+
+ else -> {}
+ }
}
}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/PackageInstaller.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/PackageInstaller.kt
index d679a579..52536299 100644
--- a/app/src/main/kotlin/com/aliucord/manager/installer/util/PackageInstaller.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/PackageInstaller.kt
@@ -1,52 +1,10 @@
package com.aliucord.manager.installer.util
-import android.annotation.SuppressLint
-import android.app.Application
-import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.content.pm.PackageInstaller.SessionParams
-import android.content.pm.PackageManager
import android.net.Uri
-import android.os.Build
-import com.aliucord.manager.installer.service.InstallService
-import java.io.File
-
-fun Application.installApks(silent: Boolean = false, vararg apks: File) {
- val packageInstaller = packageManager.packageInstaller
- val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
- if (Build.VERSION.SDK_INT >= 31) {
- setInstallScenario(PackageManager.INSTALL_SCENARIO_FAST)
-
- if (silent) {
- setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
- }
- }
- }
-
- val sessionId = packageInstaller.createSession(params)
- val session = packageInstaller.openSession(sessionId)
-
- apks.forEach { apk ->
- session.openWrite(apk.name, 0, apk.length()).use {
- it.write(apk.readBytes())
- session.fsync(it)
- }
- }
-
- val callbackIntent = Intent(this, InstallService::class.java)
-
- @SuppressLint("UnspecifiedImmutableFlag")
- val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- PendingIntent.getService(this, 0, callbackIntent, PendingIntent.FLAG_MUTABLE)
- } else {
- PendingIntent.getService(this, 0, callbackIntent, 0)
- }
-
- session.commit(contentIntent.intentSender)
- session.close()
-}
+// TODO: move this to PMInstaller
fun Context.uninstallApk(packageName: String) {
val packageURI = Uri.parse("package:$packageName")
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI).apply {
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/Installer.kt b/app/src/main/kotlin/com/aliucord/manager/installers/Installer.kt
new file mode 100644
index 00000000..644318ef
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/Installer.kt
@@ -0,0 +1,22 @@
+package com.aliucord.manager.installers
+
+import java.io.File
+
+/**
+ * A generic installer interface that manages installing APKs
+ */
+interface Installer {
+ /**
+ * Starts an installation and forgets about it. A toast will be shown when the installation was completed.
+ * @param apks All APKs (including any splits) to merge into a single install.
+ * @param silent If this is an update, then the update will occur without user interaction.
+ */
+ fun install(apks: List, silent: Boolean = true)
+
+ /**
+ * Starts an installation and waits for it to finish with a result. A toast will be shown when the installation was completed.
+ * @param apks All APKs (including any splits) to merge into a single install.
+ * @param silent If this is an update, then the update will occur without user interaction.
+ */
+ suspend fun waitInstall(apks: List, silent: Boolean = true): InstallerResult
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/InstallerResult.kt b/app/src/main/kotlin/com/aliucord/manager/installers/InstallerResult.kt
new file mode 100644
index 00000000..174fe07b
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/InstallerResult.kt
@@ -0,0 +1,41 @@
+package com.aliucord.manager.installers
+
+import android.os.Parcelable
+import androidx.annotation.StringRes
+import kotlinx.parcelize.Parcelize
+
+/**
+ * The state of an APK installation after it has completed and cleaned up.
+ */
+sealed interface InstallerResult : Parcelable {
+ /**
+ * The installation was successfully completed.
+ */
+ @Parcelize
+ data object Success : InstallerResult
+
+ /**
+ * This installation was interrupted and the install session has been canceled.
+ * @param systemTriggered Whether the cancellation happened from the system (ie. clicked cancel on the install prompt)
+ * Otherwise, this was caused by a coroutine cancellation.
+ */
+ @Parcelize
+ data class Cancelled(val systemTriggered: Boolean) : InstallerResult
+
+ /**
+ * This installation encountered an error and has been aborted.
+ * All implementors should implement [Parcelable].
+ */
+ abstract class Error : InstallerResult {
+ /**
+ * Loggable error that should not be shown to the user.
+ */
+ abstract val debugReason: String
+
+ /**
+ * Simplified + translatable user facing errors.
+ */
+ @get:StringRes
+ abstract val localizedReason: Int
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstaller.kt b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstaller.kt
new file mode 100644
index 00000000..9892ee97
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstaller.kt
@@ -0,0 +1,126 @@
+package com.aliucord.manager.installers.pm
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.*
+import android.content.pm.PackageInstaller.SessionParams
+import android.os.Build
+import android.os.Process
+import android.util.Log
+import com.aliucord.manager.BuildConfig
+import com.aliucord.manager.installers.Installer
+import com.aliucord.manager.installers.InstallerResult
+import kotlinx.coroutines.suspendCancellableCoroutine
+import java.io.File
+
+/**
+ * APK installer using the [PackageInstaller] from the system's [PackageManager] service.
+ */
+class PMInstaller(
+ val context: Context,
+) : Installer {
+ init {
+ val pkgInstaller = context.packageManager.packageInstaller
+
+ // Destroy all open sessions that may have not been previously cleaned up
+ for (session in pkgInstaller.mySessions) {
+ Log.d(BuildConfig.TAG, "Deleting PackageInstaller session ${session.sessionId}")
+ pkgInstaller.abandonSession(session.sessionId)
+ }
+ }
+
+ override fun install(apks: List, silent: Boolean) {
+ startInstall(createInstallSession(silent), apks, false)
+ }
+
+ override suspend fun waitInstall(apks: List, silent: Boolean): InstallerResult {
+ val sessionId = createInstallSession(silent)
+
+ return suspendCancellableCoroutine { continuation ->
+ // This will receive parsed data forwarded by PMIntentReceiver
+ val relayReceiver = PMResultReceiver(sessionId, continuation)
+
+ // Unregister PMResultReceiver when this coroutine finishes
+ // additionally, cancel the install session entirely
+ continuation.invokeOnCancellation {
+ context.unregisterReceiver(relayReceiver)
+ context.packageManager.packageInstaller.abandonSession(sessionId)
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ if (Build.VERSION.SDK_INT >= 33) {
+ context.registerReceiver(relayReceiver, relayReceiver.filter, Context.RECEIVER_NOT_EXPORTED)
+ } else {
+ context.registerReceiver(relayReceiver, relayReceiver.filter)
+ }
+
+ startInstall(sessionId, apks, relay = true)
+ }
+ }
+
+ /**
+ * Starts a [PackageInstaller] session with the necessary params.
+ * @param silent If this is an update, then the update will occur without user interaction.
+ * @return The open install session id.
+ */
+ private fun createInstallSession(silent: Boolean): Int {
+ val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
+ setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO)
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ setOriginatingUid(Process.myUid())
+ }
+
+ if (Build.VERSION.SDK_INT >= 26) {
+ setInstallReason(PackageManager.INSTALL_REASON_USER)
+ }
+
+ if (Build.VERSION.SDK_INT >= 31) {
+ setInstallScenario(PackageManager.INSTALL_SCENARIO_FAST)
+
+ if (silent) {
+ setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= 34) {
+ setPackageSource(PackageInstaller.PACKAGE_SOURCE_OTHER)
+ }
+ }
+
+ return context.packageManager.packageInstaller.createSession(params)
+ }
+
+ /**
+ * Start a [PackageInstaller] session for installation.
+ * @param apks The apks to install
+ * @param relay Whether to use the [PMResultReceiver] flow.
+ */
+ private fun startInstall(sessionId: Int, apks: List, relay: Boolean) {
+ val callbackIntent = Intent(context, PMIntentReceiver::class.java)
+ .putExtra(PMIntentReceiver.EXTRA_SESSION_ID, sessionId)
+ .putExtra(PMIntentReceiver.EXTRA_RELAY_ENABLED, relay)
+
+ val pendingIntent = PendingIntent.getBroadcast(
+ /* context = */ context,
+ /* requestCode = */ 0,
+ /* intent = */ callbackIntent,
+ /* flags = */ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+
+ context.packageManager.packageInstaller.openSession(sessionId).use { session ->
+ val bufferSize = 1 * 1024 * 1024 // 1MiB
+
+ for (apk in apks) {
+ session.openWrite(apk.name, 0, apk.length()).use { outStream ->
+ apk.inputStream().use { it.copyTo(outStream, bufferSize) }
+ session.fsync(outStream)
+ }
+ }
+
+ session.commit(pendingIntent.intentSender)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstallerError.kt b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstallerError.kt
new file mode 100644
index 00000000..b7676d12
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMInstallerError.kt
@@ -0,0 +1,39 @@
+package com.aliucord.manager.installers.pm
+
+import android.content.pm.PackageInstaller
+import android.os.Parcelable
+import com.aliucord.manager.R
+import com.aliucord.manager.installers.InstallerResult
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
+
+/**
+ * Translates the errors returned by PackageInstaller's [PackageInstaller.EXTRA_STATUS]
+ * that is captured by a receiver into something human readable.
+ */
+@Parcelize
+data class PMInstallerError(val status: Int) : InstallerResult.Error(), Parcelable {
+ @IgnoredOnParcel
+ override val debugReason = when (status) {
+ PackageInstaller.STATUS_FAILURE -> "Unknown failure"
+ PackageInstaller.STATUS_FAILURE_BLOCKED -> "Blocked"
+ PackageInstaller.STATUS_FAILURE_INVALID -> "Invalid package"
+ PackageInstaller.STATUS_FAILURE_CONFLICT -> "Package conflict"
+ PackageInstaller.STATUS_FAILURE_STORAGE -> "Storage error"
+ PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> "Device incompatibility"
+ /* PackageInstaller.STATUS_FAILURE_TIMEOUT */ 8 -> "Operation timeout"
+ else -> "Unknown code ($status)"
+ }
+
+ @IgnoredOnParcel
+ override val localizedReason = when (status) {
+ PackageInstaller.STATUS_FAILURE -> R.string.install_error_unknown
+ PackageInstaller.STATUS_FAILURE_BLOCKED -> R.string.install_error_blocked
+ PackageInstaller.STATUS_FAILURE_INVALID -> R.string.install_error_invalid
+ PackageInstaller.STATUS_FAILURE_CONFLICT -> R.string.install_error_conflict
+ PackageInstaller.STATUS_FAILURE_STORAGE -> R.string.install_error_storage
+ PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> R.string.install_error_incompatible
+ /* PackageInstaller.STATUS_FAILURE_TIMEOUT */ 8 -> R.string.install_error_timeout
+ else -> R.string.install_error_unknown
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMIntentReceiver.kt b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMIntentReceiver.kt
new file mode 100644
index 00000000..c464e2fc
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMIntentReceiver.kt
@@ -0,0 +1,73 @@
+package com.aliucord.manager.installers.pm
+
+import android.content.*
+import android.content.pm.PackageInstaller
+import android.util.Log
+import com.aliucord.manager.BuildConfig
+import com.aliucord.manager.R
+import com.aliucord.manager.installers.InstallerResult
+import com.aliucord.manager.util.showToast
+
+class PMIntentReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val realSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
+ val expectedSessionId = intent.getIntExtra(EXTRA_SESSION_ID, -2)
+
+ if (realSessionId != expectedSessionId) return
+
+ val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)
+ val installerResult = when (status) {
+ -999 -> {
+ // Invalid intent
+ null
+ }
+
+ PackageInstaller.STATUS_PENDING_USER_ACTION -> {
+ @Suppress("DEPRECATION")
+ val confirmationIntent = intent
+ .getParcelableExtra(Intent.EXTRA_INTENT)!!
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ context.startActivity(confirmationIntent)
+
+ null // no result yet
+ }
+
+ PackageInstaller.STATUS_SUCCESS -> {
+ context.showToast(R.string.installer_success)
+ InstallerResult.Success
+ }
+
+ PackageInstaller.STATUS_FAILURE_ABORTED -> {
+ context.showToast(R.string.installer_aborted)
+ InstallerResult.Cancelled(systemTriggered = true)
+ }
+
+ else -> {
+ Log.w(BuildConfig.TAG, "Install failed with error code $status")
+
+ if (status <= PackageInstaller.STATUS_SUCCESS)
+ null // Unknown status code (not an error)
+ else {
+ PMInstallerError(status).also {
+ context.showToast(it.localizedReason)
+ }
+ }
+ }
+ }
+
+ // Forward result to PMResultReceiver if relaying is enabled and have a real result
+ if (installerResult != null && intent.getBooleanExtra(EXTRA_RELAY_ENABLED, false)) {
+ val relayIntent = Intent(PMResultReceiver.ACTION_RECEIVE_RESULT)
+ .putExtra(PMResultReceiver.EXTRA_RESULT, installerResult)
+ .putExtra(PMResultReceiver.EXTRA_SESSION_ID, realSessionId)
+
+ context.sendBroadcast(relayIntent)
+ }
+ }
+
+ companion object {
+ const val EXTRA_RELAY_ENABLED = "relayEnabled"
+ const val EXTRA_SESSION_ID = "expectedSessionId"
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMResultReceiver.kt b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMResultReceiver.kt
new file mode 100644
index 00000000..f9f17ec6
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/installers/pm/PMResultReceiver.kt
@@ -0,0 +1,33 @@
+package com.aliucord.manager.installers.pm
+
+import android.content.*
+import com.aliucord.manager.installers.InstallerResult
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+
+class PMResultReceiver(
+ private val sessionId: Int,
+ private val continuation: Continuation,
+) : BroadcastReceiver() {
+ /**
+ * The intent filter this receiver should be registered with to work properly.
+ */
+ val filter = IntentFilter(ACTION_RECEIVE_RESULT)
+
+ @Suppress("DEPRECATION")
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != ACTION_RECEIVE_RESULT) return
+ if (intent.getIntExtra(EXTRA_SESSION_ID, -1) != sessionId) return
+
+ val result = intent.getParcelableExtra(EXTRA_RESULT)
+ ?: return
+
+ continuation.resume(result)
+ }
+
+ companion object {
+ const val ACTION_RECEIVE_RESULT = "com.aliucord.manager.RELAY_PM_RESULT"
+ const val EXTRA_RESULT = "installerResult"
+ const val EXTRA_SESSION_ID = "sessionId"
+ }
+}
diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/InstallerManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/InstallerManager.kt
new file mode 100644
index 00000000..c6a4d905
--- /dev/null
+++ b/app/src/main/kotlin/com/aliucord/manager/manager/InstallerManager.kt
@@ -0,0 +1,29 @@
+package com.aliucord.manager.manager
+
+import com.aliucord.manager.installers.Installer
+import com.aliucord.manager.installers.pm.PMInstaller
+import org.koin.core.annotation.KoinInternalApi
+import org.koin.core.component.KoinComponent
+import kotlin.reflect.KClass
+
+/**
+ * Handle providing the correct install manager based on preferences.
+ */
+class InstallerManager(
+ private val prefs: PreferencesManager,
+) : KoinComponent {
+ fun getActiveInstaller(): Installer =
+ getInstaller(prefs.installer)
+
+ @OptIn(KoinInternalApi::class)
+ fun getInstaller(type: InstallerSetting): Installer =
+ getKoin().scopeRegistry.rootScope.get(clazz = type.installerClass)
+}
+
+enum class InstallerSetting(
+ // @StringRes
+ // private val localizedName: Int,
+ val installerClass: KClass,
+) {
+ PM(PMInstaller::class),
+}
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 a15db049..f49d71fb 100644
--- a/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/manager/PreferencesManager.kt
@@ -9,6 +9,7 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager
var dynamicColor by booleanPreference("dynamic_color", true)
var replaceIcon by booleanPreference("replace_icon", true)
var devMode by booleanPreference("dev_mode", false)
+ var installer by enumPreference("installer", InstallerSetting.PM)
var debuggable by booleanPreference("debuggable", false)
var appName by stringPreference("app_name", "Aliucord")
var packageName by stringPreference("package_name", "com.aliucord")
diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt
index 80252f80..37d30fd9 100644
--- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt
+++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt
@@ -12,6 +12,8 @@ import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.KotlinInstallRunner
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.base.Step
+import com.aliucord.manager.installer.steps.base.StepState
+import com.aliucord.manager.installer.steps.install.InstallStep
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.ui.util.toUnsafeImmutable
import com.aliucord.manager.util.*
@@ -79,13 +81,20 @@ class InstallModel(
// Execute all the steps and catch any errors
when (val error = runner.executeAll()) {
- // Successfully installed
null -> {
- mutableState.value = InstallScreenState.Success
-
- // Wait 20s before returning to Home
- delay(5000)
- mutableState.value = InstallScreenState.CloseScreen
+ // If install step is marked skipped then the installation was manually aborted
+ // and if so, immediately close install screen
+ if (runner.getStep().state == StepState.Skipped) {
+ mutableState.value = InstallScreenState.CloseScreen
+ }
+ // At this point, the installation has successfully completed
+ else {
+ mutableState.value = InstallScreenState.Success
+
+ // Wait 20s before returning to Home
+ delay(5000)
+ mutableState.value = InstallScreenState.CloseScreen
+ }
}
else -> {
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 55338188..e32a7ad3 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
@@ -6,8 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.domain.repository.GithubRepository
-import com.aliucord.manager.installer.util.installApks
-import com.aliucord.manager.manager.DownloadManager
+import com.aliucord.manager.manager.*
import com.aliucord.manager.network.utils.SemVer
import com.aliucord.manager.network.utils.getOrNull
import kotlinx.coroutines.Dispatchers
@@ -16,6 +15,7 @@ import kotlinx.coroutines.launch
class UpdaterViewModel(
private val github: GithubRepository,
private val downloadManager: DownloadManager,
+ private val installers: InstallerManager,
private val application: Application,
) : ViewModel() {
var showDialog by mutableStateOf(false)
@@ -42,7 +42,10 @@ class UpdaterViewModel(
it.delete()
downloadManager.download(url, it)
- application.installApks(silent = true, it)
+ installers.getInstaller(InstallerSetting.PM).install(
+ apks = listOf(it),
+ silent = true,
+ )
it.delete()
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6e7a5cdc..0de92053 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -89,12 +89,11 @@
Saved to %s
Failed to save %s
Successfully installed Aliucord
- Aborted Aliucord installation
+ Cancelled Aliucord installation
Failed to verify download
Please uninstall your current version of Aliucord in order to continue!
Failed to install (Unknown reason)
- Failed to install (error code %d)
Installation was blocked
One or more APKs were invalid or corrupt
Conflicts with an existing app, usually due to mismatched signatures