Show list of packages that we could restore data for
This commit is contained in:
parent
96a4642f4f
commit
2f352fe828
6 changed files with 119 additions and 44 deletions
|
@ -72,5 +72,5 @@ class NotificationBackupObserver(context: Context, private val userInitiated: Bo
|
||||||
fun getAppName(pm: PackageManager, packageId: String): CharSequence {
|
fun getAppName(pm: PackageManager, packageId: String): CharSequence {
|
||||||
if (packageId == MAGIC_PACKAGE_MANAGER) return packageId
|
if (packageId == MAGIC_PACKAGE_MANAGER) return packageId
|
||||||
val appInfo = pm.getApplicationInfo(packageId, 0)
|
val appInfo = pm.getApplicationInfo(packageId, 0)
|
||||||
return pm.getApplicationLabel(appInfo)
|
return pm.getApplicationLabel(appInfo) ?: packageId
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.stevesoltys.seedvault.restore
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.INVISIBLE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
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.RestoreProgressAdapter.PackageViewHolder
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||||
|
|
||||||
|
private val items = LinkedList<String>().apply { add(MAGIC_PACKAGE_MANAGER) }
|
||||||
|
private var isComplete = false
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
|
||||||
|
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false)
|
||||||
|
return PackageViewHolder(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PackageViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position], position == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(packageName: String) {
|
||||||
|
items.addFirst(packageName)
|
||||||
|
notifyItemInserted(0)
|
||||||
|
notifyItemRangeChanged(1, items.size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setComplete() {
|
||||||
|
isComplete = true
|
||||||
|
notifyItemChanged(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class PackageViewHolder(v: View) : ViewHolder(v) {
|
||||||
|
|
||||||
|
private val context = v.context
|
||||||
|
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 appStatus: ImageView = v.findViewById(R.id.appStatus)
|
||||||
|
private val progressBar: ProgressBar = v.findViewById(R.id.progressBar)
|
||||||
|
|
||||||
|
init {
|
||||||
|
appStatus.setImageResource(R.drawable.ic_check_green)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(item: String, isLast: Boolean) {
|
||||||
|
if (item == MAGIC_PACKAGE_MANAGER) {
|
||||||
|
appIcon.setImageDrawable(pm.getApplicationIcon("android"))
|
||||||
|
appName.text = context.getString(R.string.restore_magic_package)
|
||||||
|
} else {
|
||||||
|
appIcon.setImageDrawable(pm.getApplicationIcon(item))
|
||||||
|
appName.text = getAppName(pm, item)
|
||||||
|
}
|
||||||
|
if (isLast && !isComplete) {
|
||||||
|
appStatus.visibility = INVISIBLE
|
||||||
|
progressBar.visibility = VISIBLE
|
||||||
|
} else {
|
||||||
|
appStatus.visibility = VISIBLE
|
||||||
|
progressBar.visibility = INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,16 +4,15 @@ import android.app.Activity.RESULT_OK
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.VISIBLE
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||||
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.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.getAppName
|
|
||||||
import com.stevesoltys.seedvault.isDebugBuild
|
|
||||||
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
||||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
|
@ -21,11 +20,27 @@ class RestoreProgressFragment : Fragment() {
|
||||||
|
|
||||||
private val viewModel: RestoreViewModel by sharedViewModel()
|
private val viewModel: RestoreViewModel by sharedViewModel()
|
||||||
|
|
||||||
|
private val layoutManager = LinearLayoutManager(context)
|
||||||
|
private val adapter = RestoreProgressAdapter()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.fragment_restore_progress, container, false)
|
return inflater.inflate(R.layout.fragment_restore_progress, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
appList.apply {
|
||||||
|
layoutManager = this@RestoreProgressFragment.layoutManager
|
||||||
|
adapter = this@RestoreProgressFragment.adapter
|
||||||
|
addItemDecoration(DividerItemDecoration(context, VERTICAL))
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setOnClickListener {
|
||||||
|
requireActivity().setResult(RESULT_OK)
|
||||||
|
requireActivity().finishAfterTransition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
@ -37,27 +52,23 @@ class RestoreProgressFragment : Fragment() {
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.restoreProgress.observe(this, Observer { currentPackage ->
|
viewModel.restoreProgress.observe(this, Observer { currentPackage ->
|
||||||
val appName = getAppName(requireActivity().packageManager, currentPackage)
|
// TODO maybe check against metadata and add packages that weren't called as failed in the end
|
||||||
val displayName = if (isDebugBuild()) "$appName (${currentPackage})" else appName
|
val position = layoutManager.findFirstVisibleItemPosition()
|
||||||
currentPackageView.text = getString(R.string.restore_current_package, displayName)
|
adapter.add(currentPackage)
|
||||||
|
if (position == 0) layoutManager.scrollToPosition(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.restoreBackupResult.observe(this, Observer { finished ->
|
viewModel.restoreBackupResult.observe(this, Observer { finished ->
|
||||||
progressBar.visibility = INVISIBLE
|
adapter.setComplete()
|
||||||
button.visibility = VISIBLE
|
button.isEnabled = true
|
||||||
if (finished.hasError()) {
|
if (finished.hasError()) {
|
||||||
currentPackageView.text = finished.errorMsg
|
backupNameView.text = finished.errorMsg
|
||||||
currentPackageView.setTextColor(getColor(requireContext(), R.color.red))
|
backupNameView.setTextColor(getColor(requireContext(), R.color.red))
|
||||||
} else {
|
} else {
|
||||||
currentPackageView.text = getString(R.string.restore_finished_success)
|
backupNameView.text = getString(R.string.restore_finished_success)
|
||||||
}
|
}
|
||||||
activity?.window?.clearFlags(FLAG_KEEP_SCREEN_ON)
|
activity?.window?.clearFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
})
|
})
|
||||||
|
|
||||||
button.setOnClickListener {
|
|
||||||
requireActivity().setResult(RESULT_OK)
|
|
||||||
requireActivity().finishAfterTransition()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,30 +41,16 @@
|
||||||
app:layout_constraintTop_toBottomOf="@+id/titleView"
|
app:layout_constraintTop_toBottomOf="@+id/titleView"
|
||||||
tools:text="Pixel 2 XL" />
|
tools:text="Pixel 2 XL" />
|
||||||
|
|
||||||
<TextView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/currentPackageView"
|
android:id="@+id/appList"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:textColor="?android:textColorSecondary"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
|
|
||||||
tools:text="@string/restore_current_package" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:indeterminate="true"
|
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/currentPackageView" />
|
tools:listitem="@layout/list_item_app_status" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button"
|
android:id="@+id/button"
|
||||||
|
@ -74,11 +60,10 @@
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:text="@string/restore_finished_button"
|
android:text="@string/restore_finished_button"
|
||||||
android:visibility="invisible"
|
android:enabled="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/progressBar"
|
app:layout_constraintTop_toBottomOf="@+id/appList"
|
||||||
app:layout_constraintVertical_bias="1.0"
|
app:layout_constraintVertical_bias="1.0" />
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -92,7 +92,8 @@
|
||||||
<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_current_package">Restoring %s…</string>
|
<string name="restore_current_package">Restoring %s…</string>
|
||||||
<string name="restore_finished_success">Restore complete.</string>
|
<string name="restore_magic_package">System Package Manager</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>
|
||||||
<string name="storage_internal_warning_title">Warning</string>
|
<string name="storage_internal_warning_title">Warning</string>
|
||||||
|
|
|
@ -143,7 +143,7 @@ internal class ApkRestoreTest : RestoreTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `test successful run`(@TempDir tmpDir: Path) = runBlocking {
|
fun `test successful run`(@TempDir tmpDir: Path) = runBlocking {
|
||||||
val installResult = MutableInstallResult(1).apply {
|
val installResult = MutableInstallResult(1).apply {
|
||||||
put(packageName, ApkRestoreResult(progress = 1, total = 1, status = SUCCEEDED))
|
put(packageName, ApkRestoreResult(packageName, progress = 1, total = 1, status = SUCCEEDED))
|
||||||
}
|
}
|
||||||
|
|
||||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
|
|
Loading…
Reference in a new issue