When an app could not be restored, show the likely reason for it
This commit is contained in:
parent
74183d40d6
commit
22be36e2a7
7 changed files with 109 additions and 30 deletions
|
@ -3,7 +3,6 @@ package com.stevesoltys.seedvault
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
|
@ -13,9 +12,10 @@ import org.koin.core.inject
|
||||||
|
|
||||||
private val TAG = NotificationBackupObserver::class.java.simpleName
|
private val TAG = NotificationBackupObserver::class.java.simpleName
|
||||||
|
|
||||||
class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
|
class NotificationBackupObserver(
|
||||||
|
private val context: Context,
|
||||||
|
private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
|
||||||
|
|
||||||
private val pm = context.packageManager
|
|
||||||
private val nm: BackupNotificationManager by inject()
|
private val nm: BackupNotificationManager by inject()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,15 +63,15 @@ class NotificationBackupObserver(context: Context, private val userInitiated: Bo
|
||||||
nm.onBackupFinished()
|
nm.onBackupFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppName(packageId: String): CharSequence = getAppName(pm, packageId)
|
private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAppName(pm: PackageManager, packageId: String): CharSequence {
|
fun getAppName(context: Context, packageId: String): CharSequence {
|
||||||
if (packageId == MAGIC_PACKAGE_MANAGER) return packageId
|
if (packageId == MAGIC_PACKAGE_MANAGER) return context.getString(R.string.restore_magic_package)
|
||||||
return try {
|
return try {
|
||||||
val appInfo = pm.getApplicationInfo(packageId, 0)
|
val appInfo = context.packageManager.getApplicationInfo(packageId, 0)
|
||||||
pm.getApplicationLabel(appInfo) ?: packageId
|
context.packageManager.getApplicationLabel(appInfo) ?: packageId
|
||||||
} catch (e: NameNotFoundException) {
|
} catch (e: NameNotFoundException) {
|
||||||
packageId
|
packageId
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,7 @@ package com.stevesoltys.seedvault.restore
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.*
|
||||||
import android.view.View.VISIBLE
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
@ -14,9 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.getAppName
|
import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
|
||||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
|
|
||||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
|
|
||||||
import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
|
import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -64,38 +61,68 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||||
private val pm = context.packageManager
|
private val pm = context.packageManager
|
||||||
private val appIcon: ImageView = v.findViewById(R.id.appIcon)
|
private val appIcon: ImageView = v.findViewById(R.id.appIcon)
|
||||||
private val appName: TextView = v.findViewById(R.id.appName)
|
private val appName: TextView = v.findViewById(R.id.appName)
|
||||||
|
private val appInfo: TextView = v.findViewById(R.id.appInfo)
|
||||||
private val appStatus: ImageView = v.findViewById(R.id.appStatus)
|
private val appStatus: ImageView = v.findViewById(R.id.appStatus)
|
||||||
private val progressBar: ProgressBar = v.findViewById(R.id.progressBar)
|
private val progressBar: ProgressBar = v.findViewById(R.id.progressBar)
|
||||||
|
|
||||||
fun bind(item: AppRestoreResult) {
|
fun bind(item: AppRestoreResult) {
|
||||||
|
appName.text = item.name
|
||||||
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
|
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
|
||||||
appIcon.setImageResource(R.drawable.ic_launcher_default)
|
appIcon.setImageResource(R.drawable.ic_launcher_default)
|
||||||
appName.text = context.getString(R.string.restore_magic_package)
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
appIcon.setImageDrawable(pm.getApplicationIcon(item.packageName))
|
appIcon.setImageDrawable(pm.getApplicationIcon(item.packageName))
|
||||||
} catch (e: NameNotFoundException) {
|
} catch (e: NameNotFoundException) {
|
||||||
appIcon.setImageResource(R.drawable.ic_launcher_default)
|
appIcon.setImageResource(R.drawable.ic_launcher_default)
|
||||||
}
|
}
|
||||||
appName.text = getAppName(pm, item.packageName)
|
|
||||||
}
|
}
|
||||||
if (item.status == IN_PROGRESS) {
|
if (item.status == IN_PROGRESS) {
|
||||||
|
appInfo.visibility = GONE
|
||||||
appStatus.visibility = INVISIBLE
|
appStatus.visibility = INVISIBLE
|
||||||
progressBar.visibility = VISIBLE
|
progressBar.visibility = VISIBLE
|
||||||
} else {
|
} else {
|
||||||
appStatus.setImageResource(
|
|
||||||
if (item.status == SUCCEEDED) R.drawable.ic_check_green
|
|
||||||
else R.drawable.ic_cancel_red
|
|
||||||
)
|
|
||||||
appStatus.visibility = VISIBLE
|
appStatus.visibility = VISIBLE
|
||||||
progressBar.visibility = INVISIBLE
|
progressBar.visibility = INVISIBLE
|
||||||
|
appInfo.visibility = GONE
|
||||||
|
when (item.status) {
|
||||||
|
SUCCEEDED -> {
|
||||||
|
appStatus.setImageResource(R.drawable.ic_check_green)
|
||||||
}
|
}
|
||||||
|
FAILED -> {
|
||||||
|
appStatus.setImageResource(R.drawable.ic_cancel_red)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
appStatus.setImageResource(R.drawable.ic_error_yellow)
|
||||||
|
appInfo.text = getInfo(item.status)
|
||||||
|
appInfo.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInfo(status: AppRestoreStatus): String = when(status) {
|
||||||
|
FAILED_NO_DATA -> context.getString(R.string.restore_app_no_data)
|
||||||
|
FAILED_NOT_ALLOWED -> context.getString(R.string.restore_app_not_allowed)
|
||||||
|
FAILED_NOT_INSTALLED -> context.getString(R.string.restore_app_not_installed)
|
||||||
|
FAILED_QUOTA_EXCEEDED -> context.getString(R.string.restore_app_quota_exceeded)
|
||||||
|
else -> "Please report a bug after you read this."
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum class AppRestoreStatus { IN_PROGRESS, SUCCEEDED, FAILED }
|
internal enum class AppRestoreStatus {
|
||||||
|
IN_PROGRESS,
|
||||||
|
SUCCEEDED,
|
||||||
|
FAILED,
|
||||||
|
FAILED_NO_DATA,
|
||||||
|
FAILED_NOT_ALLOWED,
|
||||||
|
FAILED_QUOTA_EXCEEDED,
|
||||||
|
FAILED_NOT_INSTALLED,
|
||||||
|
}
|
||||||
|
|
||||||
internal data class AppRestoreResult(val packageName: String, val status: AppRestoreStatus)
|
internal data class AppRestoreResult(
|
||||||
|
val packageName: String,
|
||||||
|
val name: CharSequence,
|
||||||
|
val status: AppRestoreStatus)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.backup.IBackupManager
|
||||||
import android.app.backup.IRestoreObserver
|
import android.app.backup.IRestoreObserver
|
||||||
import android.app.backup.IRestoreSession
|
import android.app.backup.IRestoreSession
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -18,6 +19,8 @@ import com.stevesoltys.seedvault.BackupMonitor
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
|
import com.stevesoltys.seedvault.getAppName
|
||||||
|
import com.stevesoltys.seedvault.metadata.PackageState.*
|
||||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
|
import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
|
||||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
|
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
|
||||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
|
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
|
||||||
|
@ -78,7 +81,7 @@ internal class RestoreViewModel(
|
||||||
|
|
||||||
private val mRestoreProgress = MutableLiveData<LinkedList<AppRestoreResult>>().apply {
|
private val mRestoreProgress = MutableLiveData<LinkedList<AppRestoreResult>>().apply {
|
||||||
value = LinkedList<AppRestoreResult>().apply {
|
value = LinkedList<AppRestoreResult>().apply {
|
||||||
add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, IN_PROGRESS))
|
add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, getAppName(app, MAGIC_PACKAGE_MANAGER), IN_PROGRESS))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
|
internal val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
|
||||||
|
@ -180,19 +183,39 @@ internal class RestoreViewModel(
|
||||||
// check previous package first and change status
|
// check previous package first and change status
|
||||||
updateLatestPackage(list)
|
updateLatestPackage(list)
|
||||||
// add current package
|
// add current package
|
||||||
list.addFirst(AppRestoreResult(packageName, IN_PROGRESS))
|
list.addFirst(AppRestoreResult(packageName, getAppName(app, packageName), IN_PROGRESS))
|
||||||
mRestoreProgress.postValue(list)
|
mRestoreProgress.postValue(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private fun updateLatestPackage(list: LinkedList<AppRestoreResult>) {
|
private fun updateLatestPackage(list: LinkedList<AppRestoreResult>) {
|
||||||
val latestResult = list[0]
|
val latestResult = list[0]
|
||||||
if (restoreCoordinator.isFailedPackage(latestResult.packageName)) {
|
if (restoreCoordinator.isFailedPackage(latestResult.packageName)) {
|
||||||
list[0] = latestResult.copy(status = FAILED)
|
list[0] = latestResult.copy(status = getFailedStatus(latestResult.packageName))
|
||||||
} else {
|
} else {
|
||||||
list[0] = latestResult.copy(status = SUCCEEDED)
|
list[0] = latestResult.copy(status = SUCCEEDED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun getFailedStatus(packageName: String, restorableBackup: RestorableBackup = chosenRestorableBackup.value!!): AppRestoreStatus {
|
||||||
|
val metadata = restorableBackup.packageMetadataMap[packageName] ?: return FAILED
|
||||||
|
return when (metadata.state) {
|
||||||
|
NO_DATA -> FAILED_NO_DATA
|
||||||
|
NOT_ALLOWED -> FAILED_NOT_ALLOWED
|
||||||
|
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
|
||||||
|
UNKNOWN_ERROR -> FAILED
|
||||||
|
APK_AND_DATA -> {
|
||||||
|
try {
|
||||||
|
app.packageManager.getPackageInfo(packageName, 0)
|
||||||
|
FAILED
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
FAILED_NOT_INSTALLED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun onRestoreComplete(result: RestoreBackupResult) {
|
private fun onRestoreComplete(result: RestoreBackupResult) {
|
||||||
// update status of latest package
|
// update status of latest package
|
||||||
|
@ -205,7 +228,9 @@ internal class RestoreViewModel(
|
||||||
val expectedPackages = restorableBackup.packageMetadataMap.keys
|
val expectedPackages = restorableBackup.packageMetadataMap.keys
|
||||||
expectedPackages.removeAll(seenPackages)
|
expectedPackages.removeAll(seenPackages)
|
||||||
for (packageName: String in expectedPackages) {
|
for (packageName: String in expectedPackages) {
|
||||||
list.addFirst(AppRestoreResult(packageName, FAILED))
|
// TODO don't add if it was a NO_DATA system app
|
||||||
|
val failedStatus = getFailedStatus(packageName, restorableBackup)
|
||||||
|
list.addFirst(AppRestoreResult(packageName, getAppName(app, packageName), failedStatus))
|
||||||
}
|
}
|
||||||
mRestoreProgress.postValue(list)
|
mRestoreProgress.postValue(list)
|
||||||
|
|
||||||
|
|
9
app/src/main/res/drawable/ic_error_yellow.xml
Normal file
9
app/src/main/res/drawable/ic_error_yellow.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/yellow"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
|
||||||
|
</vector>
|
|
@ -25,16 +25,29 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/appInfo"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/appStatus"
|
app:layout_constraintEnd_toStartOf="@+id/appStatus"
|
||||||
app:layout_constraintStart_toEndOf="@+id/appIcon"
|
app:layout_constraintStart_toEndOf="@+id/appIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Seedvault Backup" />
|
tools:text="Seedvault Backup" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/appInfo"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/appName"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/appName"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/appName"
|
||||||
|
tools:text="Some additional information about why the app could not be installed or its data not restored."
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/appStatus"
|
android:id="@+id/appStatus"
|
||||||
android:layout_width="48dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="32dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
@ -44,8 +57,8 @@
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
style="?android:attr/progressBarStyle"
|
style="?android:attr/progressBarStyle"
|
||||||
android:layout_width="48dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="32dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
|
@ -4,4 +4,5 @@
|
||||||
<color name="divider">#8A000000</color>
|
<color name="divider">#8A000000</color>
|
||||||
<color name="green">#558B2F</color>
|
<color name="green">#558B2F</color>
|
||||||
<color name="red">#D32F2F</color>
|
<color name="red">#D32F2F</color>
|
||||||
|
<color name="yellow">#F9A825</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -92,6 +92,10 @@
|
||||||
<string name="restore_next">Next</string>
|
<string name="restore_next">Next</string>
|
||||||
<string name="restore_restoring">Restoring Backup</string>
|
<string name="restore_restoring">Restoring Backup</string>
|
||||||
<string name="restore_magic_package">System Package Manager</string>
|
<string name="restore_magic_package">System Package Manager</string>
|
||||||
|
<string name="restore_app_no_data">App reported no data for backup</string>
|
||||||
|
<string name="restore_app_not_allowed">Data backup was not allowed</string>
|
||||||
|
<string name="restore_app_not_installed">App not installed</string>
|
||||||
|
<string name="restore_app_quota_exceeded">Backup quota exceeded</string>
|
||||||
<string name="restore_finished_success">Restore complete</string>
|
<string name="restore_finished_success">Restore complete</string>
|
||||||
<string name="restore_finished_error">An error occurred while restoring the backup.</string>
|
<string name="restore_finished_error">An error occurred while restoring the backup.</string>
|
||||||
<string name="restore_finished_button">Finish</string>
|
<string name="restore_finished_button">Finish</string>
|
||||||
|
|
Loading…
Reference in a new issue