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:
parent
1a81e2ddd6
commit
a9402f4644
4 changed files with 60 additions and 11 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue