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?) { private fun install(cachedApk: File, installerPackageName: String?) {
val sessionParams = SessionParams(MODE_FULL_INSTALL).apply { val sessionParams = SessionParams(MODE_FULL_INSTALL).apply {
setInstallerPackageName(installerPackageName) 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. // Don't set more sessionParams intentionally here.
// We saw strange permission issues when doing setInstallReason() or setting installFlags. // 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
import android.content.Intent.ACTION_VIEW import android.content.Intent.ACTION_VIEW
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP 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.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
@ -23,7 +22,7 @@ internal class InstallIntentCreator(
fun getIntent(packageName: CharSequence, installerPackageName: CharSequence?): Intent { fun getIntent(packageName: CharSequence, installerPackageName: CharSequence?): Intent {
val i = Intent(ACTION_VIEW, Uri.parse("market://details?id=$packageName")).apply { 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_CLEAR_TOP)
addFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) addFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
} }
@ -39,14 +38,18 @@ internal class InstallIntentCreator(
if (installerPackageName == null) return null if (installerPackageName == null) return null
val packageName = installerToPackage[installerPackageName] ?: return null val packageName = installerToPackage[installerPackageName] ?: return null
val isInstalled = isPackageInstalled.getOrPut(packageName) { val isInstalled = isPackageInstalled.getOrPut(packageName) {
try { packageManager.isInstalled(packageName)
packageManager.getPackageInfo(packageName, 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
} }
return if (isInstalled) packageName else null 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 package com.stevesoltys.seedvault.restore.install
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -7,6 +9,7 @@ import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@ -115,9 +118,33 @@ class InstallProgressFragment : Fragment(), InstallItemListener {
} }
override fun onFailedItemClicked(item: ApkInstallResult) { override fun onFailedItemClicked(item: ApkInstallResult) {
val i = installAppLauncher.launch(item)
viewModel.installIntentCreator.getIntent(item.packageName, item.installerPackageName) }
startActivity(i)
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 package com.stevesoltys.seedvault.restore.install
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
internal interface InstallResult { internal interface InstallResult {
@ -45,6 +47,12 @@ internal interface InstallResult {
* and we need to treat all packages as failed that haven't been processed. * and we need to treat all packages as failed that haven't been processed.
*/ */
fun queuedToFailed() 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 { internal class MutableInstallResult(override val total: Int) : InstallResult {
@ -89,6 +97,15 @@ internal class MutableInstallResult(override val total: Int) : InstallResult {
return this 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( data class ApkInstallResult(