From 22be36e2a72e6e8a0e29c954be2c28a8f9c98191 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 13 Jan 2020 18:17:08 -0300 Subject: [PATCH] When an app could not be restored, show the likely reason for it --- .../seedvault/NotificationBackupObserver.kt | 16 +++--- .../restore/RestoreProgressAdapter.kt | 53 ++++++++++++++----- .../seedvault/restore/RestoreViewModel.kt | 33 ++++++++++-- app/src/main/res/drawable/ic_error_yellow.xml | 9 ++++ .../main/res/layout/list_item_app_status.xml | 23 ++++++-- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 4 ++ 7 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 app/src/main/res/drawable/ic_error_yellow.xml diff --git a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt index 9174b0db..c3394065 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt @@ -3,7 +3,6 @@ package com.stevesoltys.seedvault import android.app.backup.BackupProgress import android.app.backup.IBackupObserver import android.content.Context -import android.content.pm.PackageManager import android.content.pm.PackageManager.NameNotFoundException import android.util.Log import android.util.Log.INFO @@ -13,9 +12,10 @@ import org.koin.core.inject 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() /** @@ -63,15 +63,15 @@ class NotificationBackupObserver(context: Context, private val userInitiated: Bo 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 { - if (packageId == MAGIC_PACKAGE_MANAGER) return packageId +fun getAppName(context: Context, packageId: String): CharSequence { + if (packageId == MAGIC_PACKAGE_MANAGER) return context.getString(R.string.restore_magic_package) return try { - val appInfo = pm.getApplicationInfo(packageId, 0) - pm.getApplicationLabel(appInfo) ?: packageId + val appInfo = context.packageManager.getApplicationInfo(packageId, 0) + context.packageManager.getApplicationLabel(appInfo) ?: packageId } catch (e: NameNotFoundException) { packageId } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt index 442025d7..c1a1d575 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt @@ -3,8 +3,7 @@ package com.stevesoltys.seedvault.restore import android.content.pm.PackageManager.NameNotFoundException import android.view.LayoutInflater import android.view.View -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar @@ -14,9 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.getAppName -import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS -import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED +import com.stevesoltys.seedvault.restore.AppRestoreStatus.* import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder import java.util.* @@ -64,38 +61,68 @@ internal class RestoreProgressAdapter : Adapter() { private val pm = context.packageManager private val appIcon: ImageView = v.findViewById(R.id.appIcon) 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 progressBar: ProgressBar = v.findViewById(R.id.progressBar) fun bind(item: AppRestoreResult) { + appName.text = item.name if (item.packageName == MAGIC_PACKAGE_MANAGER) { appIcon.setImageResource(R.drawable.ic_launcher_default) - appName.text = context.getString(R.string.restore_magic_package) } else { try { appIcon.setImageDrawable(pm.getApplicationIcon(item.packageName)) } catch (e: NameNotFoundException) { appIcon.setImageResource(R.drawable.ic_launcher_default) } - appName.text = getAppName(pm, item.packageName) } if (item.status == IN_PROGRESS) { + appInfo.visibility = GONE appStatus.visibility = INVISIBLE progressBar.visibility = VISIBLE } else { - appStatus.setImageResource( - if (item.status == SUCCEEDED) R.drawable.ic_check_green - else R.drawable.ic_cancel_red - ) appStatus.visibility = VISIBLE 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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index bd6554c4..0dffd8fd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -5,6 +5,7 @@ import android.app.backup.IBackupManager import android.app.backup.IRestoreObserver import android.app.backup.IRestoreSession import android.app.backup.RestoreSet +import android.content.pm.PackageManager import android.os.RemoteException import android.os.UserHandle import android.util.Log @@ -18,6 +19,8 @@ import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R 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.DisplayFragment.RESTORE_APPS import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP @@ -78,7 +81,7 @@ internal class RestoreViewModel( private val mRestoreProgress = MutableLiveData>().apply { value = LinkedList().apply { - add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, IN_PROGRESS)) + add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, getAppName(app, MAGIC_PACKAGE_MANAGER), IN_PROGRESS)) } } internal val restoreProgress: LiveData> get() = mRestoreProgress @@ -180,19 +183,39 @@ internal class RestoreViewModel( // check previous package first and change status updateLatestPackage(list) // add current package - list.addFirst(AppRestoreResult(packageName, IN_PROGRESS)) + list.addFirst(AppRestoreResult(packageName, getAppName(app, packageName), IN_PROGRESS)) mRestoreProgress.postValue(list) } + @WorkerThread private fun updateLatestPackage(list: LinkedList) { val latestResult = list[0] if (restoreCoordinator.isFailedPackage(latestResult.packageName)) { - list[0] = latestResult.copy(status = FAILED) + list[0] = latestResult.copy(status = getFailedStatus(latestResult.packageName)) } else { 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 private fun onRestoreComplete(result: RestoreBackupResult) { // update status of latest package @@ -205,7 +228,9 @@ internal class RestoreViewModel( val expectedPackages = restorableBackup.packageMetadataMap.keys expectedPackages.removeAll(seenPackages) 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) diff --git a/app/src/main/res/drawable/ic_error_yellow.xml b/app/src/main/res/drawable/ic_error_yellow.xml new file mode 100644 index 00000000..a0fb86b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_error_yellow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/list_item_app_status.xml b/app/src/main/res/layout/list_item_app_status.xml index 2c67efb1..6fd59700 100644 --- a/app/src/main/res/layout/list_item_app_status.xml +++ b/app/src/main/res/layout/list_item_app_status.xml @@ -25,16 +25,29 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/appInfo" app:layout_constraintEnd_toStartOf="@+id/appStatus" app:layout_constraintStart_toEndOf="@+id/appIcon" app:layout_constraintTop_toTopOf="parent" tools:text="Seedvault Backup" /> + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 6723fca7..5956cfd7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,4 +4,5 @@ #8A000000 #558B2F #D32F2F + #F9A825 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 556159d4..ceaeb399 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,6 +92,10 @@ Next Restoring Backup System Package Manager + App reported no data for backup + Data backup was not allowed + App not installed + Backup quota exceeded Restore complete An error occurred while restoring the backup. Finish