Added a Storage Service Playground screen.
This commit is contained in:
parent
1b2c0db693
commit
59403e7da8
11 changed files with 467 additions and 5 deletions
|
@ -157,7 +157,7 @@ object BackupRepository {
|
|||
|
||||
/**
|
||||
* Checks whether or not we do not have enough storage space for our remaining attachments to be downloaded.
|
||||
* Called from the attachment / thumbnail download jobs.
|
||||
* Caller from the attachment / thumbnail download jobs.
|
||||
*/
|
||||
fun checkForOutOfStorageError(tag: String): Boolean {
|
||||
val availableSpace = getFreeStorageSpace()
|
||||
|
|
|
@ -178,6 +178,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
|||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Storage Service Playground"),
|
||||
summary = DSLSettingsText.from("Test and view storage service stuff."),
|
||||
onClick = {
|
||||
findNavController().safeNavigate(InternalSettingsFragmentDirections.actionInternalSettingsFragmentToInternalStorageServicePlaygroundFragment())
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from("'Internal Details' button"),
|
||||
summary = DSLSettingsText.from("Show a button in conversation settings that lets you see more information about a user."),
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.storage
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.Rows.TextAndLabel
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.util.Hex
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.internal.storage.InternalStorageServicePlaygroundViewModel.OneOffEvent
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.storage.RecordIkm
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord
|
||||
|
||||
class InternalStorageServicePlaygroundFragment : ComposeFragment() {
|
||||
|
||||
val viewModel: InternalStorageServicePlaygroundViewModel by viewModels()
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val manifest by viewModel.manifest
|
||||
val storageRecords by viewModel.storageRecords
|
||||
val oneOffEvent by viewModel.oneOffEvents
|
||||
var forceSsreToggled by remember { mutableStateOf(SignalStore.internal.forceSsre2Capability) }
|
||||
|
||||
Screen(
|
||||
onBackPressed = { findNavController().popBackStack() },
|
||||
manifest = manifest,
|
||||
storageRecords = storageRecords,
|
||||
oneOffEvent = oneOffEvent,
|
||||
forceSsreCapability = forceSsreToggled,
|
||||
onForceSsreToggled = { checked ->
|
||||
SignalStore.internal.forceSsre2Capability = checked
|
||||
forceSsreToggled = checked
|
||||
},
|
||||
onViewTabSelected = { viewModel.onViewTabSelected() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Screen(
|
||||
manifest: SignalStorageManifest,
|
||||
storageRecords: List<SignalStorageRecord>,
|
||||
forceSsreCapability: Boolean,
|
||||
oneOffEvent: OneOffEvent,
|
||||
onForceSsreToggled: (Boolean) -> Unit = {},
|
||||
onViewTabSelected: () -> Unit = {},
|
||||
onBackPressed: () -> Unit = {}
|
||||
) {
|
||||
var tabIndex by remember { mutableIntStateOf(0) }
|
||||
val tabs = listOf("Tools", "View")
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column {
|
||||
TopAppBar(
|
||||
title = { Text("Storage Service Playground") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackPressed) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_arrow_left_24),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
TabRow(selectedTabIndex = tabIndex) {
|
||||
tabs.forEachIndexed { index, tab ->
|
||||
Tab(
|
||||
text = { Text(tab) },
|
||||
selected = index == tabIndex,
|
||||
onClick = {
|
||||
tabIndex = index
|
||||
if (tabIndex == 1) {
|
||||
onViewTabSelected()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) { contentPadding ->
|
||||
Surface(modifier = Modifier.padding(contentPadding)) {
|
||||
when (tabIndex) {
|
||||
0 -> ToolScreen(
|
||||
forceSsreCapability = forceSsreCapability,
|
||||
onForceSsreToggled = onForceSsreToggled
|
||||
)
|
||||
1 -> ViewScreen(
|
||||
manifest = manifest,
|
||||
storageRecords = storageRecords,
|
||||
oneOffEvent = oneOffEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolScreen(
|
||||
forceSsreCapability: Boolean,
|
||||
onForceSsreToggled: (Boolean) -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
ActionRow("Enqueue StorageSyncJob", "Just a normal syncing operation.") {
|
||||
AppDependencies.jobManager.add(StorageSyncJob())
|
||||
}
|
||||
|
||||
ActionRow("Enqueue StorageForcePushJob", "Forces your local state over the remote.") {
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
}
|
||||
|
||||
ActionRow("Reset local manifest", "Makes us think we're not at the latest version (and erases RecordIkm).") {
|
||||
SignalStore.storageService.manifest = SignalStorageManifest.EMPTY
|
||||
}
|
||||
|
||||
ActionRow("Set initial storage key", "Initializes it to something random. Will cause a decryption failure.") {
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = StorageKey(Util.getSecretBytes(32))
|
||||
}
|
||||
|
||||
ActionRow("Clear initial storage key", "Sets it to null.") {
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = null
|
||||
}
|
||||
|
||||
Rows.ToggleRow(
|
||||
text = "Force SSRE2 Capability",
|
||||
checked = forceSsreCapability,
|
||||
onCheckChanged = onForceSsreToggled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ViewScreen(
|
||||
manifest: SignalStorageManifest,
|
||||
storageRecords: List<SignalStorageRecord>,
|
||||
oneOffEvent: OneOffEvent
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(oneOffEvent) {
|
||||
when (oneOffEvent) {
|
||||
OneOffEvent.None -> Unit
|
||||
OneOffEvent.ManifestDecryptionError -> {
|
||||
Toast.makeText(context, "Failed to decrypt manifest!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
OneOffEvent.StorageRecordDecryptionError -> {
|
||||
Toast.makeText(context, "Failed to decrypt storage records!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxHeight().padding(16.dp)
|
||||
) {
|
||||
item(key = "manifest") {
|
||||
ManifestRow(manifest)
|
||||
Dividers.Default()
|
||||
}
|
||||
storageRecords.forEach { record ->
|
||||
item(key = Hex.toStringCondensed(record.id.raw)) {
|
||||
StorageRecordRow(record)
|
||||
Dividers.Default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManifestRow(manifest: SignalStorageManifest) {
|
||||
Column {
|
||||
ManifestItemRow("Version", manifest.versionString)
|
||||
ManifestItemRow("RecordIkm", manifest.recordIkm?.value?.let { Hex.toStringCondensed(it) } ?: "null")
|
||||
ManifestItemRow("Total ID count", manifest.storageIds.size.toString())
|
||||
ManifestItemRow("Unknown ID count", manifest.storageIds.filter { it.isUnknown }.size.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManifestItemRow(title: String, value: String) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(title + ":", fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(value)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StorageRecordRow(record: SignalStorageRecord) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
when {
|
||||
record.proto.account != null -> {
|
||||
Column {
|
||||
Text("Account", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
record.proto.contact != null -> {
|
||||
Column {
|
||||
Text("Contact", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
record.proto.groupV1 != null -> {
|
||||
Column {
|
||||
Text("GV1", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
record.proto.groupV2 != null -> {
|
||||
Column {
|
||||
Text("GV2", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
record.proto.callLink != null -> {
|
||||
Column {
|
||||
Text("Call Link", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
record.proto.storyDistributionList != null -> {
|
||||
Column {
|
||||
Text("Distribution List", fontWeight = FontWeight.Bold)
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Column {
|
||||
Text("Unknown!")
|
||||
ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionRow(title: String, subtitle: String, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(Rows.defaultPadding())
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextAndLabel(text = title, label = subtitle)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
RunButton { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RunButton(onClick: () -> Unit) {
|
||||
Buttons.LargeTonal(onClick = onClick) {
|
||||
Text("Run")
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun ScreenPreview() {
|
||||
Previews.Preview {
|
||||
Screen(
|
||||
forceSsreCapability = true,
|
||||
manifest = SignalStorageManifest.EMPTY,
|
||||
storageRecords = emptyList(),
|
||||
oneOffEvent = OneOffEvent.None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun ViewScreenPreview() {
|
||||
val storageRecords = listOf(
|
||||
SignalStorageRecord(
|
||||
id = StorageId.forContact(byteArrayOf(1)),
|
||||
proto = StorageRecord(
|
||||
contact = ContactRecord()
|
||||
)
|
||||
),
|
||||
SignalStorageRecord(
|
||||
id = StorageId.forContact(byteArrayOf(2)),
|
||||
proto = StorageRecord(
|
||||
contact = ContactRecord()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Previews.Preview {
|
||||
ViewScreen(
|
||||
manifest = SignalStorageManifest(
|
||||
version = 43,
|
||||
sourceDeviceId = 2,
|
||||
recordIkm = RecordIkm(ByteArray(32) { 1 }),
|
||||
storageIds = storageRecords.map { it.id }
|
||||
),
|
||||
storageRecords = storageRecords,
|
||||
oneOffEvent = OneOffEvent.None
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.storage
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
|
||||
|
||||
class InternalStorageServicePlaygroundViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(InternalStorageServicePlaygroundViewModel::class)
|
||||
}
|
||||
|
||||
private val _manifest: MutableState<SignalStorageManifest> = mutableStateOf(SignalStorageManifest.EMPTY)
|
||||
val manifest: State<SignalStorageManifest>
|
||||
get() = _manifest
|
||||
|
||||
private val _storageItems: MutableState<List<SignalStorageRecord>> = mutableStateOf(emptyList())
|
||||
val storageRecords: State<List<SignalStorageRecord>>
|
||||
get() = _storageItems
|
||||
|
||||
private val _oneOffEvents: MutableState<OneOffEvent> = mutableStateOf(OneOffEvent.None)
|
||||
val oneOffEvents: State<OneOffEvent>
|
||||
get() = _oneOffEvents
|
||||
|
||||
fun onViewTabSelected() {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
|
||||
val storageKey = SignalStore.storageService.storageKeyForInitialDataRestore ?: SignalStore.storageService.storageKey
|
||||
|
||||
val manifest = when (val result = repository.getStorageManifest(storageKey)) {
|
||||
is StorageServiceRepository.ManifestResult.Success -> result.manifest
|
||||
else -> {
|
||||
Log.w(TAG, "Failed to fetch manifest!")
|
||||
_oneOffEvents.value = OneOffEvent.ManifestDecryptionError
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
_manifest.value = manifest
|
||||
|
||||
val records = when (val result = repository.readStorageRecords(storageKey, manifest.recordIkm, manifest.storageIds)) {
|
||||
is StorageServiceRepository.StorageRecordResult.Success -> result.records
|
||||
else -> {
|
||||
Log.w(TAG, "Failed to fetch records!")
|
||||
_oneOffEvents.value = OneOffEvent.StorageRecordDecryptionError
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
|
||||
_storageItems.value = records
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class OneOffEvent {
|
||||
None, ManifestDecryptionError, StorageRecordDecryptionError
|
||||
}
|
||||
}
|
|
@ -88,7 +88,7 @@ class StorageRotateManifestJob private constructor(parameters: Parameters) : Job
|
|||
|
||||
return when (val result = repository.writeUnchangedManifest(storageServiceKey, manifestWithNewVersion)) {
|
||||
StorageServiceRepository.WriteStorageRecordsResult.Success -> {
|
||||
Log.i(TAG, "Successfully rotated the manifest. Clearing restore key.")
|
||||
Log.i(TAG, "Successfully rotated the manifest as version ${manifestWithNewVersion.version}.${manifestWithNewVersion.sourceDeviceId}. Clearing restore key.")
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = null
|
||||
Result.success()
|
||||
}
|
||||
|
|
|
@ -151,6 +151,8 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal
|
|||
|
||||
var webSocketShadowingStats by nullableBlobValue(WEB_SOCKET_SHADOWING_STATS, null).defaultForExternalUsers()
|
||||
|
||||
var forceSsre2Capability by booleanValue("internal.force_ssre2_capability", false).defaultForExternalUsers()
|
||||
|
||||
private fun <T> SignalStoreValueDelegate<T>.defaultForExternalUsers(): SignalStoreValueDelegate<T> {
|
||||
return this.withPrecondition { RemoteConfig.internalUser }
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.migrations
|
|||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
* Migration for when we introduce the Account Entropy Pool (AEP).
|
||||
|
@ -23,7 +25,15 @@ internal class AepMigrationJob(
|
|||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
if (!SignalStore.account.isRegistered) {
|
||||
Log.w(TAG, "Not registered! Skipping.")
|
||||
return
|
||||
}
|
||||
|
||||
AppDependencies.jobManager.add(Svr2MirrorJob())
|
||||
if (SignalStore.account.hasLinkedDevices) {
|
||||
AppDependencies.jobManager.add(MultiDeviceKeysUpdateJob())
|
||||
}
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
}
|
||||
|
||||
|
|
|
@ -322,7 +322,8 @@ class Recipient(
|
|||
val versionedExpirationTimerCapability: Capability = capabilities.versionedExpirationTimer
|
||||
|
||||
/** The user's capability to handle the new storage record encryption scheme. */
|
||||
val storageServiceEncryptionV2Capability: Capability = capabilities.storageServiceEncryptionV2
|
||||
val storageServiceEncryptionV2Capability: Capability
|
||||
get() = if (SignalStore.internal.forceSsre2Capability) Capability.SUPPORTED else capabilities.storageServiceEncryptionV2
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
|
|
|
@ -748,6 +748,9 @@
|
|||
<action
|
||||
android:id="@+id/action_internalSettingsFragment_to_internalBackupPlaygroundFragment"
|
||||
app:destination="@id/internalBackupPlaygroundFragment" />
|
||||
<action
|
||||
android:id="@+id/action_internalSettingsFragment_to_internalStorageServicePlaygroundFragment"
|
||||
app:destination="@id/internalStorageServicePlaygroundFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -800,6 +803,11 @@
|
|||
android:name="org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundFragment"
|
||||
android:label="internal_backup_playground_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/internalStorageServicePlaygroundFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.app.internal.storage.InternalStorageServicePlaygroundFragment"
|
||||
android:label="internal_storage_service_playground_fragment" />
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- App updates -->
|
||||
|
|
|
@ -8,7 +8,7 @@ import kotlin.reflect.full.memberProperties
|
|||
/**
|
||||
* Pairs a storage record with its id. Also contains some useful common methods.
|
||||
*/
|
||||
interface SignalRecord<E> {
|
||||
sealed interface SignalRecord<E> {
|
||||
val id: StorageId
|
||||
val proto: E
|
||||
|
||||
|
|
|
@ -189,7 +189,8 @@ class StorageServiceRepository(private val storageServiceApi: StorageServiceApi)
|
|||
val manifestRecord = ManifestRecord(
|
||||
sourceDevice = signalManifest.sourceDeviceId,
|
||||
version = signalManifest.version,
|
||||
identifiers = manifestIds
|
||||
identifiers = manifestIds,
|
||||
recordIkm = signalManifest.recordIkm?.value?.toByteString() ?: ByteString.EMPTY
|
||||
)
|
||||
|
||||
val manifestKey = storageKey.deriveManifestKey(signalManifest.version)
|
||||
|
|
Loading…
Add table
Reference in a new issue