Improve RestoreSet display
when user is asked to choose a backup to restore
This commit is contained in:
parent
84dc13d267
commit
7696b88a5a
8 changed files with 102 additions and 39 deletions
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue