mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Revamp restore decisions state and flesh out post registration restore options.
This commit is contained in:
committed by
Greyson Parrelli
parent
b78747fda2
commit
fe44789d88
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user