Improve RestoreSet display

when user is asked to choose a backup to restore
This commit is contained in:
Torsten Grote 2024-09-30 15:24:08 -03:00
parent 84dc13d267
commit 7696b88a5a
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
8 changed files with 102 additions and 39 deletions

View file

@ -537,6 +537,23 @@ public object SnapshotKt {
public fun hasApk(): kotlin.Boolean { public fun hasApk(): kotlin.Boolean {
return _builder.hasApk() return _builder.hasApk()
} }
/**
* <code>uint64 size = 8;</code>
*/
public var size: kotlin.Long
@JvmName("getSize")
get() = _builder.getSize()
@JvmName("setSize")
set(value) {
_builder.setSize(value)
}
/**
* <code>uint64 size = 8;</code>
*/
public fun clearSize() {
_builder.clearSize()
}
} }
} }
@kotlin.jvm.JvmName("-initializeapk") @kotlin.jvm.JvmName("-initializeapk")

View file

@ -97,6 +97,7 @@ internal class SnapshotCreator(
system = isSystemApp system = isSystemApp
launchableSystemApp = isSystemApp && launchableSystemApps.contains(packageName) launchableSystemApp = isSystemApp && launchableSystemApps.contains(packageName)
addAllChunkIds(chunkIds) addAllChunkIds(chunkIds)
size = backupData.size
} }
blobsMap.putAll(backupData.blobMap) blobsMap.putAll(backupData.blobMap)
metadataManager.onPackageBackedUp(packageInfo, backupType, backupData.size) metadataManager.onPackageBackedUp(packageInfo, backupType, backupData.size)

View file

@ -6,9 +6,9 @@
package com.stevesoltys.seedvault.restore package com.stevesoltys.seedvault.restore
import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
import android.text.format.DateUtils.HOUR_IN_MILLIS import android.text.format.DateUtils.MINUTE_IN_MILLIS
import android.text.format.DateUtils.getRelativeTimeSpanString import android.text.format.DateUtils.getRelativeTimeSpanString
import android.text.format.Formatter import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
@ -41,32 +41,40 @@ internal class RestoreSetAdapter(
inner class RestoreSetViewHolder(private val v: View) : ViewHolder(v) { inner class RestoreSetViewHolder(private val v: View) : ViewHolder(v) {
private val titleView = v.requireViewById<TextView>(R.id.titleView) private val titleView = v.requireViewById<TextView>(R.id.titleView)
private val subtitleView = v.requireViewById<TextView>(R.id.subtitleView) private val appView = v.requireViewById<TextView>(R.id.appView)
private val sizeView = v.requireViewById<TextView>(R.id.sizeView) private val apkView = v.requireViewById<TextView>(R.id.apkView)
private val timeView = v.requireViewById<TextView>(R.id.timeView)
internal fun bind(item: RestorableBackup) { internal fun bind(item: RestorableBackup) {
v.setOnClickListener { listener.onRestorableBackupClicked(item) } v.setOnClickListener { listener.onRestorableBackupClicked(item) }
titleView.text = item.name titleView.text = item.name
val lastBackup = getRelativeTime(item.time) appView.text = if (item.sizeAppData > 0) {
val setup = getRelativeTime(item.token) v.context.getString(
subtitleView.text = R.string.restore_restore_set_apps,
v.context.getString(R.string.restore_restore_set_times, lastBackup, setup) item.numAppData,
val size = item.size formatShortFileSize(v.context, item.sizeAppData),
if (size == null) {
sizeView.visibility = GONE
} else {
sizeView.text = v.context.getString(
R.string.restore_restore_set_size,
Formatter.formatShortFileSize(v.context, size),
) )
sizeView.visibility = VISIBLE } else {
v.context.getString(R.string.restore_restore_set_apps_no_size, item.numAppData)
} }
appView.visibility = if (item.numAppData > 0) VISIBLE else GONE
apkView.text = if (item.sizeApks > 0) {
v.context.getString(
R.string.restore_restore_set_apks,
item.numApks,
formatShortFileSize(v.context, item.sizeApks),
)
} else {
v.context.getString(R.string.restore_restore_set_apks_no_size, item.numApks)
}
apkView.visibility = if (item.numApks > 0) VISIBLE else GONE
timeView.text = getRelativeTime(item.time)
} }
private fun getRelativeTime(time: Long): CharSequence { private fun getRelativeTime(time: Long): CharSequence {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
return getRelativeTimeSpanString(time, now, HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE) return getRelativeTimeSpanString(time, now, MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)
} }
} }

View file

@ -7,6 +7,7 @@ package com.stevesoltys.seedvault.transport.restore
import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot
sealed class RestorableBackupResult { sealed class RestorableBackupResult {
@ -20,7 +21,6 @@ data class RestorableBackup(
val snapshot: Snapshot? = null, val snapshot: Snapshot? = null,
) { ) {
// FIXME creating this mapping is expensive, a single call can take several seconds to complete
constructor(repoId: String, snapshot: Snapshot) : this( constructor(repoId: String, snapshot: Snapshot) : this(
backupMetadata = BackupMetadata.fromSnapshot(snapshot), backupMetadata = BackupMetadata.fromSnapshot(snapshot),
repoId = repoId, repoId = repoId,
@ -40,18 +40,33 @@ data class RestorableBackup(
get() = backupMetadata.salt get() = backupMetadata.salt
val time: Long val time: Long
get() = backupMetadata.time get() = snapshot?.token ?: backupMetadata.time
val size: Long val size: Long = snapshot?.blobsMap?.values?.sumOf { it.uncompressedLength.toLong() }
get() = snapshot?.blobsMap?.values?.sumOf { it.uncompressedLength.toLong() } ?: backupMetadata.size
?: backupMetadata.size
val deviceName: String val deviceName: String
get() = backupMetadata.deviceName get() = backupMetadata.deviceName
val user: String?
get() = snapshot?.user?.takeIf { it.isNotBlank() }
val d2dBackup: Boolean val d2dBackup: Boolean
get() = backupMetadata.d2dBackup get() = backupMetadata.d2dBackup
val numAppData: Int = snapshot?.appsMap?.values?.count { it.chunkIdsCount > 0 }
?: packageMetadataMap.values.count { packageMetadata ->
packageMetadata.backupType != null && packageMetadata.state == APK_AND_DATA
}
val sizeAppData: Long = snapshot?.appsMap?.values?.sumOf { it.size }
?: packageMetadataMap.values.sumOf { it.size ?: 0L }
val numApks: Int = snapshot?.appsMap?.values?.count { it.apk.splitsCount > 0 }
?: packageMetadataMap.values.count { it.hasApk() }
val sizeApks: Long = size - sizeAppData
val packageMetadataMap: PackageMetadataMap val packageMetadataMap: PackageMetadataMap
get() = backupMetadata.packageMetadataMap get() = backupMetadata.packageMetadataMap

View file

@ -25,6 +25,7 @@ message Snapshot {
bool launchableSystemApp = 5; bool launchableSystemApp = 5;
repeated bytes chunkIds = 6; repeated bytes chunkIds = 6;
Apk apk = 7; Apk apk = 7;
uint64 size = 8;
} }
enum BackupType { enum BackupType {

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
SPDX-FileCopyrightText: 2020 The Calyx Institute SPDX-FileCopyrightText: 2020 The Calyx Institute
SPDX-License-Identifier: Apache-2.0 SPDX-License-Identifier: Apache-2.0
--> -->
@ -25,37 +24,56 @@
<TextView <TextView
android:id="@+id/titleView" android:id="@+id/titleView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:textColorPrimary"
android:textSize="18sp" android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/appView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView" app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Pixel 2 XL backup" /> tools:text="Pixel 2 XL backup" />
<TextView <TextView
android:id="@+id/subtitleView" android:id="@+id/appView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="4dp"
android:textColor="?android:attr/textColorTertiary" android:textColor="?android:textColorSecondary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/titleView" app:layout_constraintStart_toStartOf="@+id/titleView"
app:layout_constraintTop_toBottomOf="@+id/titleView" app:layout_constraintTop_toBottomOf="@+id/titleView"
tools:text="@string/restore_restore_set_times" /> tools:text="@string/restore_restore_set_apps" />
<TextView <TextView
android:id="@+id/sizeView" android:id="@+id/apkView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="4dp"
app:layout_constraintBottom_toBottomOf="parent" android:textColor="?android:textColorTertiary"
android:layout_marginBottom="16dp" android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/timeView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/titleView" app:layout_constraintStart_toStartOf="@+id/titleView"
app:layout_constraintTop_toBottomOf="@+id/subtitleView" app:layout_constraintTop_toBottomOf="@+id/appView"
tools:text="Size: 5 GB" /> tools:text="@string/restore_restore_set_apks" />
<TextView
android:id="@+id/timeView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:gravity="end"
android:textColor="?android:textColorSecondary"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/titleView"
app:layout_constraintTop_toBottomOf="@+id/apkView"
tools:text="5 days ago" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -205,8 +205,10 @@
<!-- Restore --> <!-- Restore -->
<string name="restore_title">Restore from backup</string> <string name="restore_title">Restore from backup</string>
<string name="restore_choose_restore_set">Choose a backup to restore</string> <string name="restore_choose_restore_set">Choose a backup to restore</string>
<string name="restore_restore_set_times">Last backup %1$s · First %2$s.</string> <string name="restore_restore_set_apps">Has user data for <xliff:g example="42" id="apps">%1$d</xliff:g> apps (<xliff:g example="1 GB" id="size">%2$s</xliff:g>)</string>
<string name="restore_restore_set_size">Size: <xliff:g example="1 GB" id="size">%1$s</xliff:g></string> <string name="restore_restore_set_apps_no_size">Has user data for <xliff:g example="42" id="apps">%1$d</xliff:g> apps</string>
<string name="restore_restore_set_apks">Contains <xliff:g example="42" id="apps">%1$d</xliff:g> apps (<xliff:g example="1 GB" id="size">%2$s</xliff:g>)</string>
<string name="restore_restore_set_apks_no_size">Contains <xliff:g example="42" id="apps">%1$d</xliff:g> apps</string>
<string name="restore_skip">Don\'t restore</string> <string name="restore_skip">Don\'t restore</string>
<string name="restore_skip_apps">Skip restoring apps</string> <string name="restore_skip_apps">Skip restoring apps</string>
<string name="restore_invalid_location_title">No backups found</string> <string name="restore_invalid_location_title">No backups found</string>

View file

@ -77,6 +77,7 @@ internal class SnapshotCreatorTest : TransportTest() {
assertEquals(name, s.appsMap[packageName]?.name) assertEquals(name, s.appsMap[packageName]?.name)
assertEquals(token, s.appsMap[packageName]?.time) assertEquals(token, s.appsMap[packageName]?.time)
assertEquals(Snapshot.BackupType.FULL, s.appsMap[packageName]?.type) assertEquals(Snapshot.BackupType.FULL, s.appsMap[packageName]?.type)
assertEquals(size, s.appsMap[packageName]?.size)
assertEquals(isSystem, s.appsMap[packageName]?.system) assertEquals(isSystem, s.appsMap[packageName]?.system)
assertEquals(isSystem, s.appsMap[packageName]?.launchableSystemApp) assertEquals(isSystem, s.appsMap[packageName]?.launchableSystemApp)
assertEquals(apkBackupData.chunkIds.forProto(), s.appsMap[packageName]?.chunkIdsList) assertEquals(apkBackupData.chunkIds.forProto(), s.appsMap[packageName]?.chunkIdsList)