Update app install state after user comes back from manually installing an app

This way, the list of failed apps ideally keeps shrinking, allowing the user to see which apps are still left in a failed state.
This commit is contained in:
Torsten Grote 2020-10-09 14:00:52 -03:00 committed by Chirayu Desai
parent 1a81e2ddd6
commit a9402f4644
4 changed files with 60 additions and 11 deletions

View file

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

View file

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

View file

@ -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<ApkInstallResult, CharSequence>() {
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
}
}
}

View file

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