Show app backup check success screen
This commit is contained in:
parent
96f9a8d017
commit
26063a8ef0
8 changed files with 252 additions and 9 deletions
|
@ -131,6 +131,11 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.check.AppCheckResultActivity"
|
||||||
|
android:label="@string/notification_checking_finished_title"
|
||||||
|
android:launchMode="singleTask"/>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".transport.ConfigurableBackupTransportService"
|
android:name=".transport.ConfigurableBackupTransportService"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|
|
@ -27,7 +27,6 @@ import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
internal class Checker(
|
internal class Checker(
|
||||||
private val crypto: Crypto,
|
private val crypto: Crypto,
|
||||||
private val backendManager: BackendManager,
|
private val backendManager: BackendManager,
|
||||||
|
@ -43,7 +42,10 @@ internal class Checker(
|
||||||
// TODO determine also based on backendManager
|
// TODO determine also based on backendManager
|
||||||
return Runtime.getRuntime().availableProcessors()
|
return Runtime.getRuntime().availableProcessors()
|
||||||
}
|
}
|
||||||
|
var checkerResult: CheckerResult? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
suspend fun getBackupSize(): Long {
|
suspend fun getBackupSize(): Long {
|
||||||
// get all snapshots
|
// get all snapshots
|
||||||
val folder = TopLevelFolder(crypto.repoId)
|
val folder = TopLevelFolder(crypto.repoId)
|
||||||
|
@ -63,6 +65,7 @@ internal class Checker(
|
||||||
return sizeMap.values.sumOf { it.toLong() }
|
return sizeMap.values.sumOf { it.toLong() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
suspend fun check(percent: Int) {
|
suspend fun check(percent: Int) {
|
||||||
check(percent in 0..100) { "Percent $percent out of bounds." }
|
check(percent in 0..100) { "Percent $percent out of bounds." }
|
||||||
|
|
||||||
|
@ -106,6 +109,14 @@ internal class Checker(
|
||||||
val passedTime = System.currentTimeMillis() - startTime
|
val passedTime = System.currentTimeMillis() - startTime
|
||||||
val bandwidth = size.get() / (passedTime.toDouble() / 1000).roundToLong()
|
val bandwidth = size.get() / (passedTime.toDouble() / 1000).roundToLong()
|
||||||
nm.onCheckComplete(size.get(), bandwidth)
|
nm.onCheckComplete(size.get(), bandwidth)
|
||||||
|
checkerResult = CheckerResult.Success(snapshots, percent, size.get())
|
||||||
|
this.snapshots = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
log.info { "Clearing..." }
|
||||||
|
snapshots = null
|
||||||
|
checkerResult = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBlobSample(snapshots: List<Snapshot>, percent: Int): Map<String, Blob> {
|
private fun getBlobSample(snapshots: List<Snapshot>, percent: Int): Map<String, Blob> {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.stevesoltys.seedvault.repo
|
||||||
|
|
||||||
|
import com.stevesoltys.seedvault.proto.Snapshot
|
||||||
|
|
||||||
|
sealed class CheckerResult {
|
||||||
|
data class Success(
|
||||||
|
val snapshots: List<Snapshot>,
|
||||||
|
val percent: Int,
|
||||||
|
val size: Long,
|
||||||
|
) : CheckerResult()
|
||||||
|
|
||||||
|
data class Error(
|
||||||
|
val snapshots: List<Snapshot>,
|
||||||
|
val errors: Map<String, Exception>,
|
||||||
|
) : CheckerResult()
|
||||||
|
|
||||||
|
data class GeneralError(val e: Exception) : CheckerResult()
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
|
|
||||||
internal class RestoreSetAdapter(
|
internal class RestoreSetAdapter(
|
||||||
private val listener: RestorableBackupClickListener,
|
private val listener: RestorableBackupClickListener?,
|
||||||
private val items: List<RestorableBackup>,
|
private val items: List<RestorableBackup>,
|
||||||
) : Adapter<RestoreSetViewHolder>() {
|
) : Adapter<RestoreSetViewHolder>() {
|
||||||
|
|
||||||
|
@ -46,7 +46,9 @@ internal class RestoreSetAdapter(
|
||||||
private val timeView = v.requireViewById<TextView>(R.id.timeView)
|
private val timeView = v.requireViewById<TextView>(R.id.timeView)
|
||||||
|
|
||||||
internal fun bind(item: RestorableBackup) {
|
internal fun bind(item: RestorableBackup) {
|
||||||
v.setOnClickListener { listener.onRestorableBackupClicked(item) }
|
if (listener != null) {
|
||||||
|
v.setOnClickListener { listener.onRestorableBackupClicked(item) }
|
||||||
|
}
|
||||||
titleView.text = item.name
|
titleView.text = item.name
|
||||||
|
|
||||||
appView.text = if (item.sizeAppData > 0) {
|
appView.text = if (item.sizeAppData > 0) {
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.stevesoltys.seedvault.ui.check
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.repo.Checker
|
||||||
|
import com.stevesoltys.seedvault.repo.CheckerResult
|
||||||
|
import com.stevesoltys.seedvault.restore.RestoreSetAdapter
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
|
import com.stevesoltys.seedvault.ui.BackupActivity
|
||||||
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
internal const val ACTION_FINISHED = "FINISHED"
|
||||||
|
internal const val ACTION_SHOW = "SHOW"
|
||||||
|
|
||||||
|
class AppCheckResultActivity : BackupActivity() {
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger { }
|
||||||
|
|
||||||
|
private val checker: Checker by inject()
|
||||||
|
private val notificationManager: BackupNotificationManager by inject()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) when (intent.action) {
|
||||||
|
ACTION_FINISHED -> {
|
||||||
|
notificationManager.onCheckCompleteNotificationSeen()
|
||||||
|
checker.clear()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
ACTION_SHOW -> {
|
||||||
|
notificationManager.onCheckCompleteNotificationSeen()
|
||||||
|
onActionReceived()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
log.error { "Unknown action: ${intent.action}" }
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onActionReceived() {
|
||||||
|
when (val result = checker.checkerResult) {
|
||||||
|
is CheckerResult.Success -> onSuccess(result)
|
||||||
|
is CheckerResult.Error -> {
|
||||||
|
// TODO
|
||||||
|
log.info { "snapshots: ${result.snapshots.size}, errors: ${result.errors.size}" }
|
||||||
|
}
|
||||||
|
is CheckerResult.GeneralError, null -> {
|
||||||
|
// TODO
|
||||||
|
if (result == null) log.error { "No more result" }
|
||||||
|
else log.info((result as CheckerResult.GeneralError).e) { "Error: " }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSuccess(result: CheckerResult.Success) {
|
||||||
|
setContentView(R.layout.activity_check_success)
|
||||||
|
val intro = getString(
|
||||||
|
R.string.backup_app_check_success_intro,
|
||||||
|
result.snapshots.size,
|
||||||
|
result.percent,
|
||||||
|
formatShortFileSize(this, result.size),
|
||||||
|
)
|
||||||
|
requireViewById<TextView>(R.id.introView).text = intro
|
||||||
|
|
||||||
|
val listView = requireViewById<RecyclerView>(R.id.listView)
|
||||||
|
listView.adapter = RestoreSetAdapter(
|
||||||
|
listener = null,
|
||||||
|
items = result.snapshots.map { snapshot ->
|
||||||
|
RestorableBackup("", snapshot)
|
||||||
|
}.sortedByDescending { it.time },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,8 @@
|
||||||
package com.stevesoltys.seedvault.ui.notification
|
package com.stevesoltys.seedvault.ui.notification
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.ActivityOptions
|
||||||
|
import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
@ -15,8 +17,10 @@ import android.app.NotificationManager.IMPORTANCE_LOW
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
import android.app.PendingIntent.getActivity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -34,6 +38,9 @@ import com.stevesoltys.seedvault.restore.REQUEST_CODE_UNINSTALL
|
||||||
import com.stevesoltys.seedvault.restore.RestoreActivity
|
import com.stevesoltys.seedvault.restore.RestoreActivity
|
||||||
import com.stevesoltys.seedvault.settings.ACTION_APP_STATUS_LIST
|
import com.stevesoltys.seedvault.settings.ACTION_APP_STATUS_LIST
|
||||||
import com.stevesoltys.seedvault.settings.SettingsActivity
|
import com.stevesoltys.seedvault.settings.SettingsActivity
|
||||||
|
import com.stevesoltys.seedvault.ui.check.ACTION_FINISHED
|
||||||
|
import com.stevesoltys.seedvault.ui.check.ACTION_SHOW
|
||||||
|
import com.stevesoltys.seedvault.ui.check.AppCheckResultActivity
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
|
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
|
||||||
|
@ -202,7 +209,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
val intent = Intent(context, SettingsActivity::class.java).apply {
|
val intent = Intent(context, SettingsActivity::class.java).apply {
|
||||||
action = ACTION_APP_STATUS_LIST
|
action = ACTION_APP_STATUS_LIST
|
||||||
}
|
}
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
val pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||||
val notification = Builder(context, CHANNEL_ID_SUCCESS).apply {
|
val notification = Builder(context, CHANNEL_ID_SUCCESS).apply {
|
||||||
setSmallIcon(R.drawable.ic_cloud_done)
|
setSmallIcon(R.drawable.ic_cloud_done)
|
||||||
setContentTitle(context.getString(R.string.notification_success_title))
|
setContentTitle(context.getString(R.string.notification_success_title))
|
||||||
|
@ -221,7 +228,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
|
|
||||||
fun onBackupError() {
|
fun onBackupError() {
|
||||||
val intent = Intent(context, SettingsActivity::class.java)
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
val pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||||
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
||||||
setSmallIcon(R.drawable.ic_cloud_error)
|
setSmallIcon(R.drawable.ic_cloud_error)
|
||||||
setContentTitle(context.getString(R.string.notification_failed_title))
|
setContentTitle(context.getString(R.string.notification_failed_title))
|
||||||
|
@ -241,7 +248,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun onFixableBackupError() {
|
fun onFixableBackupError() {
|
||||||
val intent = Intent(context, SettingsActivity::class.java)
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
val pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||||
val actionText = context.getString(R.string.notification_error_action)
|
val actionText = context.getString(R.string.notification_error_action)
|
||||||
val action = Action(R.drawable.ic_storage, actionText, pendingIntent)
|
val action = Action(R.drawable.ic_storage, actionText, pendingIntent)
|
||||||
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
||||||
|
@ -276,7 +283,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
|
|
||||||
fun getRestoreNotification() = Notification.Builder(context, CHANNEL_ID_RESTORE).apply {
|
fun getRestoreNotification() = Notification.Builder(context, CHANNEL_ID_RESTORE).apply {
|
||||||
val intent = Intent(context, RestoreActivity::class.java)
|
val intent = Intent(context, RestoreActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
val pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||||
setContentIntent(pendingIntent)
|
setContentIntent(pendingIntent)
|
||||||
setSmallIcon(R.drawable.ic_cloud_restore)
|
setSmallIcon(R.drawable.ic_cloud_restore)
|
||||||
setContentTitle(context.getString(R.string.notification_restore_title))
|
setContentTitle(context.getString(R.string.notification_restore_title))
|
||||||
|
@ -356,19 +363,45 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
formatShortFileSize(context, size),
|
formatShortFileSize(context, size),
|
||||||
"${formatShortFileSize(context, speed)}/s",
|
"${formatShortFileSize(context, speed)}/s",
|
||||||
)
|
)
|
||||||
|
// the background activity launch (BAL) gets restricted for setDeleteIntent()
|
||||||
|
// if we don't use these special ActivityOptions, may cause issues in future SDKs
|
||||||
|
val options = ActivityOptions.makeBasic()
|
||||||
|
.setPendingIntentCreatorBackgroundActivityStartMode(
|
||||||
|
MODE_BACKGROUND_ACTIVITY_START_ALLOWED
|
||||||
|
).toBundle()
|
||||||
|
val cIntent = Intent(context, AppCheckResultActivity::class.java).apply {
|
||||||
|
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
setAction(ACTION_SHOW)
|
||||||
|
}
|
||||||
|
val dIntent = Intent(context, AppCheckResultActivity::class.java).apply {
|
||||||
|
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
setAction(ACTION_FINISHED)
|
||||||
|
}
|
||||||
|
val contentIntent = getActivity(context, 1, cIntent, FLAG_IMMUTABLE, options)
|
||||||
|
val deleteIntent = getActivity(context, 2, dIntent, FLAG_IMMUTABLE, options)
|
||||||
|
val actionTitle = context.getString(R.string.notification_checking_action)
|
||||||
|
val action = Action.Builder(null, actionTitle, contentIntent).build()
|
||||||
val notification = Builder(context, CHANNEL_ID_CHECKING)
|
val notification = Builder(context, CHANNEL_ID_CHECKING)
|
||||||
.setContentTitle(context.getString(R.string.notification_checking_finished_title))
|
.setContentTitle(context.getString(R.string.notification_checking_finished_title))
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setSmallIcon(R.drawable.ic_cloud_done)
|
.setSmallIcon(R.drawable.ic_cloud_done)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.addAction(action)
|
||||||
|
.setDeleteIntent(deleteIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
.build()
|
.build()
|
||||||
nm.cancel(NOTIFICATION_ID_CHECKING)
|
nm.cancel(NOTIFICATION_ID_CHECKING)
|
||||||
nm.notify(NOTIFICATION_ID_CHECK_FINISHED, notification)
|
nm.notify(NOTIFICATION_ID_CHECK_FINISHED, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onCheckCompleteNotificationSeen() {
|
||||||
|
nm.cancel(NOTIFICATION_ID_CHECK_FINISHED)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun onNoMainKeyError() {
|
fun onNoMainKeyError() {
|
||||||
val intent = Intent(context, SettingsActivity::class.java)
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
val pendingIntent = getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||||
val actionText = context.getString(R.string.notification_error_action)
|
val actionText = context.getString(R.string.notification_error_action)
|
||||||
val action = Action(0, actionText, pendingIntent)
|
val action = Action(0, actionText, pendingIntent)
|
||||||
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
||||||
|
|
77
app/src/main/res/layout/activity_check_success.xml
Normal file
77
app/src/main/res/layout/activity_check_success.xml
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: 2020 The Calyx Institute
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
style="@style/SudHeaderIcon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_cloud_done"
|
||||||
|
app:tint="?android:colorAccent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleView"
|
||||||
|
style="@style/SudHeaderTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/notification_checking_finished_title"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/introView"
|
||||||
|
style="@style/SudDescription"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/backup_app_check_success_intro"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleView" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/listView"
|
||||||
|
style="@style/SudContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="0dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/disclaimerView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/introView"
|
||||||
|
tools:itemCount="4"
|
||||||
|
tools:listitem="@layout/list_item_restore_set" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/disclaimerView"
|
||||||
|
style="@style/SudDescription"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/backup_app_check_success_disclaimer"
|
||||||
|
android:textColor="?colorError"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/listView" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
|
@ -189,8 +189,13 @@
|
||||||
|
|
||||||
<string name="notification_checking_channel_title">App backup integrity check</string>
|
<string name="notification_checking_channel_title">App backup integrity check</string>
|
||||||
<string name="notification_checking_title">Checking app backups…</string>
|
<string name="notification_checking_title">Checking app backups…</string>
|
||||||
<string name="notification_checking_finished_title">App backup integrity confirmed</string>
|
<string name="notification_checking_finished_title">App backup integrity verified</string>
|
||||||
<string name="notification_checking_finished_text">Successfully checked %1$s at an average speed of %2$s.</string>
|
<string name="notification_checking_finished_text">Successfully checked %1$s at an average speed of %2$s.</string>
|
||||||
|
<string name="notification_checking_action">Details</string>
|
||||||
|
|
||||||
|
<string name="backup_app_check_success_intro">%1$d snapshots were found and %2$d%% of their data (%3$s) successfully verified:</string>
|
||||||
|
<string name="backup_app_check_success_disclaimer">Note: We can not verify whether apps include all of their data in the backup.</string>
|
||||||
|
<string name="backup_app_check_error_no_snapshots">We could not find any backup. Please run a successful backup first and then try checking again.</string>
|
||||||
|
|
||||||
<!-- App Backup and Restore State -->
|
<!-- App Backup and Restore State -->
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue