Improve app data restore process
Apps are now restored alphabetically to be consistent with the other lists. Some irrelevant apps are hidden. Under the hood, we now use an AsyncListDiffer like in the other lists.
This commit is contained in:
parent
b3f93adf77
commit
fa19261d8e
5 changed files with 114 additions and 67 deletions
|
@ -201,6 +201,7 @@ open class App : Application() {
|
||||||
|
|
||||||
const val MAGIC_PACKAGE_MANAGER: String = PACKAGE_MANAGER_SENTINEL
|
const val MAGIC_PACKAGE_MANAGER: String = PACKAGE_MANAGER_SENTINEL
|
||||||
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
|
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
|
||||||
|
const val NO_DATA_END_SENTINEL = "@end@"
|
||||||
const val GLOBAL_METADATA_KEY = "@meta@"
|
const val GLOBAL_METADATA_KEY = "@meta@"
|
||||||
const val ERROR_BACKUP_CANCELLED: Int = BackupManager.ERROR_BACKUP_CANCELLED
|
const val ERROR_BACKUP_CANCELLED: Int = BackupManager.ERROR_BACKUP_CANCELLED
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.stevesoltys.seedvault.BackupMonitor
|
import com.stevesoltys.seedvault.BackupMonitor
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
|
import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState
|
import com.stevesoltys.seedvault.metadata.PackageState
|
||||||
import com.stevesoltys.seedvault.restore.install.isInstalled
|
import com.stevesoltys.seedvault.restore.install.isInstalled
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
|
@ -37,9 +39,16 @@ import com.stevesoltys.seedvault.ui.AppBackupState.NOT_YET_BACKED_UP
|
||||||
import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED
|
import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED
|
||||||
import com.stevesoltys.seedvault.ui.notification.getAppName
|
import com.stevesoltys.seedvault.ui.notification.getAppName
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
private val TAG = AppDataRestoreManager::class.simpleName
|
private val TAG = AppDataRestoreManager::class.simpleName
|
||||||
|
|
||||||
|
internal data class AppRestoreResult(
|
||||||
|
val packageName: String,
|
||||||
|
val name: String,
|
||||||
|
val state: AppBackupState,
|
||||||
|
)
|
||||||
|
|
||||||
internal class AppDataRestoreManager(
|
internal class AppDataRestoreManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val backupManager: IBackupManager,
|
private val backupManager: IBackupManager,
|
||||||
|
@ -50,17 +59,17 @@ internal class AppDataRestoreManager(
|
||||||
private var session: IRestoreSession? = null
|
private var session: IRestoreSession? = null
|
||||||
private val monitor = BackupMonitor()
|
private val monitor = BackupMonitor()
|
||||||
|
|
||||||
private val mRestoreProgress = MutableLiveData<LinkedList<AppRestoreResult>>().apply {
|
private val mRestoreProgress = MutableLiveData(
|
||||||
value = LinkedList<AppRestoreResult>().apply {
|
LinkedList<AppRestoreResult>().apply {
|
||||||
add(
|
add(
|
||||||
AppRestoreResult(
|
AppRestoreResult(
|
||||||
packageName = MAGIC_PACKAGE_MANAGER,
|
packageName = MAGIC_PACKAGE_MANAGER,
|
||||||
name = getAppName(context, MAGIC_PACKAGE_MANAGER),
|
name = getAppName(context, MAGIC_PACKAGE_MANAGER).toString(),
|
||||||
state = IN_PROGRESS
|
state = IN_PROGRESS,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
|
val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
|
||||||
private val mRestoreBackupResult = MutableLiveData<RestoreBackupResult>()
|
private val mRestoreBackupResult = MutableLiveData<RestoreBackupResult>()
|
||||||
val restoreBackupResult: LiveData<RestoreBackupResult> get() = mRestoreBackupResult
|
val restoreBackupResult: LiveData<RestoreBackupResult> get() = mRestoreBackupResult
|
||||||
|
@ -88,13 +97,13 @@ internal class AppDataRestoreManager(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val packages = restorableBackup.packageMetadataMap.keys.toList()
|
|
||||||
val observer = RestoreObserver(
|
val observer = RestoreObserver(
|
||||||
restoreCoordinator = restoreCoordinator,
|
restoreCoordinator = restoreCoordinator,
|
||||||
restorableBackup = restorableBackup,
|
restorableBackup = restorableBackup,
|
||||||
session = session,
|
session = session,
|
||||||
packages = packages,
|
// sort packages (reverse) alphabetically, since we move from bottom to top
|
||||||
monitor = monitor
|
packages = restorableBackup.packageMetadataMap.packagesSortedByNameDescending,
|
||||||
|
monitor = monitor,
|
||||||
)
|
)
|
||||||
|
|
||||||
// We need to retrieve the restore sets before starting the restore.
|
// We need to retrieve the restore sets before starting the restore.
|
||||||
|
@ -128,9 +137,12 @@ internal class AppDataRestoreManager(
|
||||||
updateLatestPackage(list, backup)
|
updateLatestPackage(list, backup)
|
||||||
|
|
||||||
// add current package
|
// add current package
|
||||||
list.addFirst(
|
val name = getAppName(
|
||||||
AppRestoreResult(packageName, getAppName(context, packageName), IN_PROGRESS)
|
context = context,
|
||||||
|
packageName = packageName,
|
||||||
|
fallback = backup.packageMetadataMap[packageName]?.name?.toString() ?: packageName,
|
||||||
)
|
)
|
||||||
|
list.addFirst(AppRestoreResult(packageName, name.toString(), IN_PROGRESS))
|
||||||
mRestoreProgress.postValue(list)
|
mRestoreProgress.postValue(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,15 +179,26 @@ internal class AppDataRestoreManager(
|
||||||
|
|
||||||
// add missing packages as failed
|
// add missing packages as failed
|
||||||
val seenPackages = list.map { it.packageName }.toSet()
|
val seenPackages = list.map { it.packageName }.toSet()
|
||||||
val expectedPackages = backup.packageMetadataMap.keys
|
val expectedPackages =
|
||||||
|
backup.packageMetadataMap.packagesSortedByNameDescending.toMutableSet()
|
||||||
expectedPackages.removeAll(seenPackages)
|
expectedPackages.removeAll(seenPackages)
|
||||||
for (packageName: String in expectedPackages) {
|
for (packageName in expectedPackages) {
|
||||||
// TODO don't add if it was a NO_DATA system app
|
|
||||||
val failedStatus = getFailedStatus(packageName, backup)
|
val failedStatus = getFailedStatus(packageName, backup)
|
||||||
val appResult =
|
if (failedStatus == FAILED_NO_DATA &&
|
||||||
AppRestoreResult(packageName, getAppName(context, packageName), failedStatus)
|
backup.packageMetadataMap[packageName]?.isInternalSystem == true
|
||||||
|
) {
|
||||||
|
// don't add internal system apps that had NO_DATA to backup
|
||||||
|
} else {
|
||||||
|
val name = getAppName(
|
||||||
|
context = context,
|
||||||
|
packageName = packageName,
|
||||||
|
fallback = backup.packageMetadataMap[packageName]?.name?.toString()
|
||||||
|
?: packageName,
|
||||||
|
)
|
||||||
|
val appResult = AppRestoreResult(packageName, name.toString(), failedStatus)
|
||||||
list.addFirst(appResult)
|
list.addFirst(appResult)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mRestoreProgress.postValue(list)
|
mRestoreProgress.postValue(list)
|
||||||
|
|
||||||
mRestoreBackupResult.postValue(result)
|
mRestoreBackupResult.postValue(result)
|
||||||
|
@ -186,7 +209,6 @@ internal class AppDataRestoreManager(
|
||||||
session = null
|
session = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO sort apps alphabetically
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private inner class RestoreObserver(
|
private inner class RestoreObserver(
|
||||||
private val restoreCoordinator: RestoreCoordinator,
|
private val restoreCoordinator: RestoreCoordinator,
|
||||||
|
@ -244,6 +266,7 @@ internal class AppDataRestoreManager(
|
||||||
val token = backupMetadata.token
|
val token = backupMetadata.token
|
||||||
val result = session.restorePackages(token, this, packageChunk, monitor)
|
val result = session.restorePackages(token, this, packageChunk, monitor)
|
||||||
|
|
||||||
|
@Suppress("UNRESOLVED_REFERENCE") // BackupManager.SUCCESS
|
||||||
if (result != BackupManager.SUCCESS) {
|
if (result != BackupManager.SUCCESS) {
|
||||||
Log.e(TAG, "restorePackages() returned non-zero value: $result")
|
Log.e(TAG, "restorePackages() returned non-zero value: $result")
|
||||||
}
|
}
|
||||||
|
@ -295,6 +318,7 @@ internal class AppDataRestoreManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRestoreResult(): RestoreBackupResult {
|
private fun getRestoreResult(): RestoreBackupResult {
|
||||||
|
@Suppress("UNRESOLVED_REFERENCE") // BackupManager.SUCCESS
|
||||||
val failedChunks = chunkResults
|
val failedChunks = chunkResults
|
||||||
.filter { it.value != BackupManager.SUCCESS }
|
.filter { it.value != BackupManager.SUCCESS }
|
||||||
.map { "chunk ${it.key} failed with error ${it.value}" }
|
.map { "chunk ${it.key} failed with error ${it.value}" }
|
||||||
|
@ -310,4 +334,15 @@ internal class AppDataRestoreManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val PackageMetadataMap.packagesSortedByNameDescending: List<String>
|
||||||
|
get() {
|
||||||
|
return asIterable().sortedByDescending { (packageName, metadata) ->
|
||||||
|
// sort packages (reverse) alphabetically, since we move from bottom to top
|
||||||
|
(metadata.name?.toString() ?: packageName).lowercase(Locale.getDefault())
|
||||||
|
}.mapNotNull {
|
||||||
|
// don't try to restore this helper package, as it doesn't really exist
|
||||||
|
if (it.key == NO_DATA_END_SENTINEL) null else it.key
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,40 @@
|
||||||
package com.stevesoltys.seedvault.restore
|
package com.stevesoltys.seedvault.restore
|
||||||
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
|
import androidx.recyclerview.widget.DiffUtil.ItemCallback
|
||||||
import androidx.recyclerview.widget.RecyclerView.Adapter
|
import androidx.recyclerview.widget.RecyclerView.Adapter
|
||||||
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.restore.RestoreProgressAdapter.PackageViewHolder
|
import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
|
||||||
import com.stevesoltys.seedvault.ui.AppBackupState
|
|
||||||
import com.stevesoltys.seedvault.ui.AppViewHolder
|
import com.stevesoltys.seedvault.ui.AppViewHolder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
internal class RestoreProgressAdapter(
|
||||||
|
val scope: CoroutineScope,
|
||||||
|
val iconLoader: suspend (AppRestoreResult, (Drawable) -> Unit) -> Unit,
|
||||||
|
) : Adapter<PackageViewHolder>() {
|
||||||
|
|
||||||
private val items = LinkedList<AppRestoreResult>()
|
private val diffCallback = object : ItemCallback<AppRestoreResult>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: AppRestoreResult,
|
||||||
|
newItem: AppRestoreResult,
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.packageName == newItem.packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(old: AppRestoreResult, new: AppRestoreResult): Boolean {
|
||||||
|
return old.name == new.name && old.state == new.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val differ = AsyncListDiffer(this, diffCallback)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
|
||||||
val v = LayoutInflater.from(parent.context)
|
val v = LayoutInflater.from(parent.context)
|
||||||
|
@ -28,37 +47,24 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||||
return PackageViewHolder(v)
|
return PackageViewHolder(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PackageViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: PackageViewHolder, position: Int) {
|
||||||
holder.bind(items[position])
|
holder.bind(differ.currentList[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(newItems: LinkedList<AppRestoreResult>) {
|
fun update(newItems: LinkedList<AppRestoreResult>, callback: Runnable) {
|
||||||
val diffResult = DiffUtil.calculateDiff(Diff(items, newItems))
|
// add .toList(), because [AppDataRestoreManager] still re-uses the same list,
|
||||||
items.clear()
|
// but AsyncListDiffer needs a new one.
|
||||||
items.addAll(newItems)
|
differ.submitList(newItems.toList(), callback)
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Diff(
|
override fun onViewRecycled(holder: PackageViewHolder) {
|
||||||
private val oldItems: LinkedList<AppRestoreResult>,
|
holder.iconJob?.cancel()
|
||||||
private val newItems: LinkedList<AppRestoreResult>,
|
|
||||||
) : DiffUtil.Callback() {
|
|
||||||
|
|
||||||
override fun getOldListSize() = oldItems.size
|
|
||||||
override fun getNewListSize() = newItems.size
|
|
||||||
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
||||||
return oldItems[oldItemPosition].packageName == newItems[newItemPosition].packageName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
inner class PackageViewHolder(v: View) : AppViewHolder(v) {
|
||||||
return oldItems[oldItemPosition] == newItems[newItemPosition]
|
var iconJob: Job? = null
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PackageViewHolder(v: View) : AppViewHolder(v) {
|
|
||||||
fun bind(item: AppRestoreResult) {
|
fun bind(item: AppRestoreResult) {
|
||||||
appName.text = item.name
|
appName.text = item.name
|
||||||
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
|
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
|
||||||
|
@ -67,7 +73,11 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||||
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)
|
iconJob = scope.launch {
|
||||||
|
iconLoader(item) { bitmap ->
|
||||||
|
appIcon.setImageDrawable(bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState(item.state, true)
|
setState(item.state, true)
|
||||||
|
@ -75,9 +85,3 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class AppRestoreResult(
|
|
||||||
val packageName: String,
|
|
||||||
val name: CharSequence,
|
|
||||||
val state: AppBackupState,
|
|
||||||
)
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.restore
|
package com.stevesoltys.seedvault.restore
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -16,6 +17,7 @@ import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat.getColor
|
import androidx.core.content.ContextCompat.getColor
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
@ -27,7 +29,7 @@ class RestoreProgressFragment : Fragment() {
|
||||||
private val viewModel: RestoreViewModel by sharedViewModel()
|
private val viewModel: RestoreViewModel by sharedViewModel()
|
||||||
|
|
||||||
private val layoutManager = LinearLayoutManager(context)
|
private val layoutManager = LinearLayoutManager(context)
|
||||||
private val adapter = RestoreProgressAdapter()
|
private val adapter = RestoreProgressAdapter(lifecycleScope, this::loadIcon)
|
||||||
|
|
||||||
private lateinit var progressBar: ProgressBar
|
private lateinit var progressBar: ProgressBar
|
||||||
private lateinit var titleView: TextView
|
private lateinit var titleView: TextView
|
||||||
|
@ -67,17 +69,20 @@ class RestoreProgressFragment : Fragment() {
|
||||||
// decryption will fail when the device is locked, so keep the screen on to prevent locking
|
// decryption will fail when the device is locked, so keep the screen on to prevent locking
|
||||||
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
|
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
viewModel.chosenRestorableBackup.observe(viewLifecycleOwner, { restorableBackup ->
|
viewModel.chosenRestorableBackup.observe(viewLifecycleOwner) { restorableBackup ->
|
||||||
backupNameView.text = restorableBackup.name
|
backupNameView.text = restorableBackup.name
|
||||||
progressBar.max = restorableBackup.packageMetadataMap.size
|
progressBar.max = restorableBackup.packageMetadataMap.size
|
||||||
})
|
}
|
||||||
|
|
||||||
viewModel.restoreProgress.observe(viewLifecycleOwner, { list ->
|
viewModel.restoreProgress.observe(viewLifecycleOwner) { list ->
|
||||||
stayScrolledAtTop { adapter.update(list) }
|
|
||||||
progressBar.progress = list.size
|
progressBar.progress = list.size
|
||||||
})
|
val position = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
adapter.update(list) {
|
||||||
|
if (position == 0) layoutManager.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.restoreBackupResult.observe(viewLifecycleOwner, { finished ->
|
viewModel.restoreBackupResult.observe(viewLifecycleOwner) { finished ->
|
||||||
button.isEnabled = true
|
button.isEnabled = true
|
||||||
if (finished.hasError()) {
|
if (finished.hasError()) {
|
||||||
backupNameView.text = finished.errorMsg
|
backupNameView.text = finished.errorMsg
|
||||||
|
@ -87,7 +92,7 @@ class RestoreProgressFragment : Fragment() {
|
||||||
onRestoreFinished()
|
onRestoreFinished()
|
||||||
}
|
}
|
||||||
activity?.window?.clearFlags(FLAG_KEEP_SCREEN_ON)
|
activity?.window?.clearFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRestoreFinished() {
|
private fun onRestoreFinished() {
|
||||||
|
@ -103,10 +108,8 @@ class RestoreProgressFragment : Fragment() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stayScrolledAtTop(add: () -> Unit) {
|
private suspend fun loadIcon(item: AppRestoreResult, callback: (Drawable) -> Unit) {
|
||||||
val position = layoutManager.findFirstVisibleItemPosition()
|
viewModel.loadIcon(item.packageName, callback)
|
||||||
add.invoke()
|
|
||||||
if (position == 0) layoutManager.scrollToPosition(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,14 +167,18 @@ internal class NotificationBackupObserver(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAppName(context: Context, packageId: String): CharSequence {
|
fun getAppName(
|
||||||
if (packageId == MAGIC_PACKAGE_MANAGER || packageId.startsWith("@")) {
|
context: Context,
|
||||||
|
packageName: String,
|
||||||
|
fallback: String = packageName,
|
||||||
|
): CharSequence {
|
||||||
|
if (packageName == MAGIC_PACKAGE_MANAGER || packageName.startsWith("@")) {
|
||||||
return context.getString(R.string.restore_magic_package)
|
return context.getString(R.string.restore_magic_package)
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
val appInfo = context.packageManager.getApplicationInfo(packageId, 0)
|
val appInfo = context.packageManager.getApplicationInfo(packageName, 0)
|
||||||
context.packageManager.getApplicationLabel(appInfo)
|
context.packageManager.getApplicationLabel(appInfo)
|
||||||
} catch (e: NameNotFoundException) {
|
} catch (e: NameNotFoundException) {
|
||||||
packageId
|
fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue