diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt index c9ec4513..992c8245 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt @@ -59,6 +59,8 @@ internal class ApkInstaller(private val context: Context) { private fun install(cachedApk: File, installerPackageName: String?) { val sessionParams = SessionParams(MODE_FULL_INSTALL).apply { setInstallerPackageName(installerPackageName) + // Setting the INSTALL_ALLOW_TEST flag here does not allow us to install test apps, + // because the flag is filtered out by PackageInstallerService. } // Don't set more sessionParams intentionally here. // We saw strange permission issues when doing setInstallReason() or setting installFlags. diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt index 3bf3fcb2..0f10c746 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt @@ -3,7 +3,6 @@ package com.stevesoltys.seedvault.restore.install import android.content.Intent import android.content.Intent.ACTION_VIEW import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED import android.content.pm.PackageManager import android.net.Uri @@ -23,7 +22,7 @@ internal class InstallIntentCreator( fun getIntent(packageName: CharSequence, installerPackageName: CharSequence?): Intent { val i = Intent(ACTION_VIEW, Uri.parse("market://details?id=$packageName")).apply { - addFlags(FLAG_ACTIVITY_NEW_TASK) + // Not using FLAG_ACTIVITY_NEW_TASK, so startActivityForResult works addFlags(FLAG_ACTIVITY_CLEAR_TOP) addFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) } @@ -39,14 +38,18 @@ internal class InstallIntentCreator( if (installerPackageName == null) return null val packageName = installerToPackage[installerPackageName] ?: return null val isInstalled = isPackageInstalled.getOrPut(packageName) { - try { - packageManager.getPackageInfo(packageName, 0) - true - } catch (e: PackageManager.NameNotFoundException) { - false - } + packageManager.isInstalled(packageName) } return if (isInstalled) packageName else null } } + +fun PackageManager.isInstalled(packageName: String): Boolean { + return try { + getPackageInfo(packageName, 0) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt index 4c8023c3..9388c7e2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt @@ -1,5 +1,7 @@ package com.stevesoltys.seedvault.restore.install +import android.content.Context +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -7,6 +9,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContract import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.Observer @@ -115,9 +118,33 @@ class InstallProgressFragment : Fragment(), InstallItemListener { } override fun onFailedItemClicked(item: ApkInstallResult) { - val i = - viewModel.installIntentCreator.getIntent(item.packageName, item.installerPackageName) - startActivity(i) + installAppLauncher.launch(item) + } + + private val installAppLauncher = registerForActivityResult(InstallApp()) { packageName -> + val result = viewModel.installResult.value ?: return@registerForActivityResult + if (result.isFinished) { + val changed = result.reCheckFailedPackage( + requireContext().packageManager, + packageName.toString() + ) + if (changed) adapter.update(result.getNotQueued()) + } + } + + private inner class InstallApp : ActivityResultContract() { + private lateinit var packageName: CharSequence + override fun createIntent(context: Context, input: ApkInstallResult): Intent { + packageName = input.packageName + return viewModel.installIntentCreator.getIntent( + input.packageName, + input.installerPackageName + ) + } + + override fun parseResult(resultCode: Int, intent: Intent?): CharSequence { + return packageName + } } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt index 51005651..b37cc851 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt @@ -1,9 +1,11 @@ package com.stevesoltys.seedvault.restore.install +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED +import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED import java.util.concurrent.ConcurrentHashMap internal interface InstallResult { @@ -45,6 +47,12 @@ internal interface InstallResult { * and we need to treat all packages as failed that haven't been processed. */ fun queuedToFailed() + + /** + * Once [isFinished] is true, this can be called to re-check a package in state [FAILED]. + * If it is now installed, the state will be changed to [SUCCEEDED] and true returned. + */ + fun reCheckFailedPackage(pm: PackageManager, packageName: String): Boolean } internal class MutableInstallResult(override val total: Int) : InstallResult { @@ -89,6 +97,15 @@ internal class MutableInstallResult(override val total: Int) : InstallResult { return this } + override fun reCheckFailedPackage(pm: PackageManager, packageName: String): Boolean { + check(isFinished) { "re-checking failed packages only allowed when finished" } + if (pm.isInstalled(packageName)) { + update(packageName) { it.copy(state = SUCCEEDED) } + return true + } + return false + } + } data class ApkInstallResult(