Revamp restore decisions state and flesh out post registration restore options.

This commit is contained in:
Cody Henthorne
2025-02-04 13:26:36 -05:00
committed by Greyson Parrelli
parent b78747fda2
commit fe44789d88
35 changed files with 1071 additions and 411 deletions

View File

@@ -1,7 +1,9 @@
package org.thoughtcrime.securesms.keyvalue
import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
class RegistrationValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
@@ -11,12 +13,13 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
private const val HAS_UPLOADED_PROFILE = "registration.has_uploaded_profile"
private const val SESSION_E164 = "registration.session_e164"
private const val SESSION_ID = "registration.session_id"
private const val SKIPPED_TRANSFER_OR_RESTORE = "registration.has_skipped_transfer_or_restore"
private const val LOCAL_REGISTRATION_DATA = "registration.local_registration_data"
private const val RESTORE_COMPLETED = "registration.backup_restore_completed"
private const val RESTORE_METHOD_TOKEN = "registration.restore_method_token"
private const val IS_OTHER_DEVICE_ANDROID = "registration.is_other_device_android"
private const val RESTORING_ON_NEW_DEVICE = "registration.restoring_on_new_device"
@VisibleForTesting
const val RESTORE_DECISION_STATE = "registration.restore_decision_state"
}
@Synchronized
@@ -26,7 +29,7 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
.putBoolean(HAS_UPLOADED_PROFILE, false)
.putBoolean(REGISTRATION_COMPLETE, false)
.putBoolean(PIN_REQUIRED, true)
.putBoolean(SKIPPED_TRANSFER_OR_RESTORE, false)
.putBlob(RESTORE_DECISION_STATE, RestoreDecisionState.Start.encode())
.commit()
}
@@ -68,23 +71,5 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
@get:JvmName("isRestoringOnNewDevice")
var restoringOnNewDevice: Boolean by booleanValue(RESTORING_ON_NEW_DEVICE, false)
fun hasSkippedTransferOrRestore(): Boolean {
return getBoolean(SKIPPED_TRANSFER_OR_RESTORE, false)
}
fun markSkippedTransferOrRestore() {
putBoolean(SKIPPED_TRANSFER_OR_RESTORE, true)
}
fun debugClearSkippedTransferOrRestore() {
putBoolean(SKIPPED_TRANSFER_OR_RESTORE, false)
}
fun hasCompletedRestore(): Boolean {
return getBoolean(RESTORE_COMPLETED, false)
}
fun markRestoreCompleted() {
putBoolean(RESTORE_COMPLETED, true)
}
var restoreDecisionState: RestoreDecisionState by protoValue(RESTORE_DECISION_STATE, RestoreDecisionState.Skipped, RestoreDecisionState.ADAPTER)
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@file:JvmName("RestoreDecisionStateUtil")
package org.thoughtcrime.securesms.keyvalue
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
/** Are we still awaiting a final decision about restore. */
val RestoreDecisionState.isDecisionPending: Boolean
get() = when (this.decisionState) {
RestoreDecisionState.State.START -> true
RestoreDecisionState.State.INTEND_TO_RESTORE -> true
RestoreDecisionState.State.NEW_ACCOUNT -> false
RestoreDecisionState.State.SKIPPED -> false
RestoreDecisionState.State.COMPLETED -> false
}
/** Has the user skiped the restore flow and continued on through normal registration. */
val RestoreDecisionState.skippedRestoreChoice: Boolean
get() = this.decisionState == RestoreDecisionState.State.START
/** Has the user indicated they want a manual remote restore but not via quick restore. */
val RestoreDecisionState.isWantingManualRemoteRestore: Boolean
get() = when (this.decisionState) {
RestoreDecisionState.State.INTEND_TO_RESTORE -> {
this.intendToRestoreData?.fromRemote == true && !this.intendToRestoreData.hasOldDevice
}
else -> false
}
/** Has a final decision been made regarding restoring. */
val RestoreDecisionState.isTerminal: Boolean
get() = !isDecisionPending
/** Start of the decision state 'machine'. Should really only be necessary on fresh install first launch. */
val RestoreDecisionState.Companion.Start: RestoreDecisionState
get() = RestoreDecisionState(RestoreDecisionState.State.START)
/** Helper to create a [RestoreDecisionState.State.INTEND_TO_RESTORE] with appropriate data. */
fun RestoreDecisionState.Companion.intendToRestore(hasOldDevice: Boolean, fromRemote: Boolean?): RestoreDecisionState {
return RestoreDecisionState(
decisionState = RestoreDecisionState.State.INTEND_TO_RESTORE,
intendToRestoreData = RestoreDecisionState.IntendToRestoreData(hasOldDevice = hasOldDevice, fromRemote = fromRemote)
)
}
/** Terminal decision made for the user if we think this is a registration without a backup */
val RestoreDecisionState.Companion.NewAccount: RestoreDecisionState
get() = RestoreDecisionState(RestoreDecisionState.State.NEW_ACCOUNT)
/** User elected not to restore any backup. */
val RestoreDecisionState.Companion.Skipped: RestoreDecisionState
get() = RestoreDecisionState(RestoreDecisionState.State.SKIPPED)
/** User elected to and successful completed restoring data in some form. */
val RestoreDecisionState.Companion.Completed: RestoreDecisionState
get() = RestoreDecisionState(RestoreDecisionState.State.COMPLETED)

View File

@@ -42,6 +42,10 @@ internal fun <M> SignalStoreValues.protoValue(key: String, adapter: ProtoAdapter
return KeyValueProtoValue(key, adapter, this.store)
}
internal fun <M> SignalStoreValues.protoValue(key: String, default: M, adapter: ProtoAdapter<M>): SignalStoreValueDelegate<M> {
return KeyValueProtoWithDefaultValue(key, default, adapter, this.store)
}
internal fun <T> SignalStoreValueDelegate<T>.withPrecondition(precondition: () -> Boolean): SignalStoreValueDelegate<T> {
return PreconditionDelegate(
delegate = this,
@@ -154,6 +158,29 @@ private class NullableBlobValue(private val key: String, default: ByteArray?, st
}
}
private class KeyValueProtoWithDefaultValue<M>(
private val key: String,
default: M,
private val adapter: ProtoAdapter<M>,
store: KeyValueStore
) : SignalStoreValueDelegate<M>(store, default) {
override fun getValue(values: KeyValueStore): M {
return if (values.containsKey(key)) {
adapter.decode(values.getBlob(key, null))
} else {
default
}
}
override fun setValue(values: KeyValueStore, value: M) {
if (value != null) {
values.beginWrite().putBlob(key, adapter.encode(value)).apply()
} else {
values.beginWrite().remove(key).apply()
}
}
}
private class KeyValueProtoValue<M>(
private val key: String,
private val adapter: ProtoAdapter<M>,