mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
Restore timestamp instead of tier during manual registration remote backup restore flow.
This commit is contained in:
@@ -23,6 +23,6 @@ enum class RegistrationCheckpoint {
|
||||
PIN_ENTERED,
|
||||
VERIFICATION_CODE_VALIDATED,
|
||||
SERVICE_REGISTRATION_COMPLETED,
|
||||
BACKUP_TIER_NOT_RESTORED,
|
||||
BACKUP_TIMESTAMP_NOT_RESTORED,
|
||||
LOCAL_REGISTRATION_COMPLETE
|
||||
}
|
||||
|
||||
@@ -27,11 +27,9 @@ import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.RestoreTimestampResult
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.ReclaimUsernameAndLinkJob
|
||||
import org.thoughtcrime.securesms.keyvalue.NewAccount
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -71,7 +69,6 @@ import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequ
|
||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.TokenNotAccepted
|
||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.UnknownError
|
||||
import org.thoughtcrime.securesms.registration.ui.toE164
|
||||
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.registrationv3.data.RegistrationRepository
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.StorageServiceRestore
|
||||
@@ -916,24 +913,27 @@ class RegistrationViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
if (SignalStore.account.restoredAccountEntropyPool) {
|
||||
Log.d(TAG, "Restoring backup tier")
|
||||
Log.d(TAG, "Restoring backup timestamp")
|
||||
var tries = 0
|
||||
while (tries < 3 && !SignalStore.backup.isBackupTierRestored) {
|
||||
while (tries < 3) {
|
||||
if (tries > 0) {
|
||||
delay(1.seconds)
|
||||
}
|
||||
BackupRepository.restoreBackupTier(SignalStore.account.requireAci())
|
||||
if (BackupRepository.restoreBackupFileTimestamp() !is RestoreTimestampResult.Failure) {
|
||||
break
|
||||
}
|
||||
tries++
|
||||
}
|
||||
}
|
||||
|
||||
refreshRemoteConfig()
|
||||
|
||||
val checkpoint = if (SignalStore.registration.restoreDecisionState.isDecisionPending &&
|
||||
val checkpoint = if (
|
||||
SignalStore.registration.restoreDecisionState.isDecisionPending &&
|
||||
SignalStore.registration.restoreDecisionState.isWantingManualRemoteRestore &&
|
||||
(!SignalStore.backup.isBackupTierRestored || SignalStore.backup.lastBackupTime == 0L)
|
||||
SignalStore.backup.lastBackupTime == 0L
|
||||
) {
|
||||
RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED
|
||||
RegistrationCheckpoint.BACKUP_TIMESTAMP_NOT_RESTORED
|
||||
} else {
|
||||
RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE
|
||||
}
|
||||
@@ -963,19 +963,19 @@ class RegistrationViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreBackupTier() {
|
||||
fun checkForBackupFile() {
|
||||
store.update {
|
||||
it.copy(inProgress = true, registrationCheckpoint = RegistrationCheckpoint.SERVICE_REGISTRATION_COMPLETED)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val start = System.currentTimeMillis()
|
||||
val tierUnknown = BackupRepository.restoreBackupTier(SignalStore.account.requireAci()) == null
|
||||
val result = BackupRepository.restoreBackupFileTimestamp()
|
||||
delay(max(0L, 500L - (System.currentTimeMillis() - start)))
|
||||
|
||||
if (tierUnknown || SignalStore.backup.lastBackupTime == 0L) {
|
||||
if (result !is RestoreTimestampResult.Success) {
|
||||
store.update {
|
||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED)
|
||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.BACKUP_TIMESTAMP_NOT_RESTORED)
|
||||
}
|
||||
} else {
|
||||
store.update {
|
||||
@@ -985,11 +985,6 @@ class RegistrationViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun completeRegistration() {
|
||||
AppDependencies.jobManager.startChain(ProfileUploadJob()).then(listOf(MultiDeviceProfileKeyUpdateJob(), MultiDeviceProfileContentUpdateJob())).enqueue()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
}
|
||||
|
||||
fun networkErrorShown() {
|
||||
store.update {
|
||||
it.copy(networkError = null)
|
||||
|
||||
@@ -67,9 +67,9 @@ class EnterBackupKeyFragment : ComposeFragment() {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
sharedViewModel
|
||||
.state
|
||||
.filter { it.registrationCheckpoint == RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED }
|
||||
.filter { it.registrationCheckpoint == RegistrationCheckpoint.BACKUP_TIMESTAMP_NOT_RESTORED }
|
||||
.collect {
|
||||
viewModel.handleBackupTierNotRestored()
|
||||
viewModel.handleBackupTimestampNotRestored()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ class EnterBackupKeyFragment : ComposeFragment() {
|
||||
state = state,
|
||||
onBackupTierRetry = {
|
||||
viewModel.incrementBackupTierRetry()
|
||||
sharedViewModel.restoreBackupTier()
|
||||
sharedViewModel.checkForBackupFile()
|
||||
},
|
||||
onAbandonRemoteRestoreAfterRegistration = {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
|
||||
@@ -86,10 +86,10 @@ class EnterBackupKeyViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleBackupTierNotRestored() {
|
||||
fun handleBackupTimestampNotRestored() {
|
||||
store.update {
|
||||
it.copy(
|
||||
showBackupTierNotRestoreError = if (SignalStore.backup.isBackupTierRestored) TierRestoreError.NOT_FOUND else TierRestoreError.NETWORK_ERROR
|
||||
showBackupTierNotRestoreError = if (SignalStore.backup.isBackupTimestampRestored) TierRestoreError.NOT_FOUND else TierRestoreError.NETWORK_ERROR
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,17 @@ import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -29,8 +33,12 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -65,6 +73,7 @@ import org.thoughtcrime.securesms.components.contactsupport.SendSupportEmailEffe
|
||||
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.shared.RegistrationScreen
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.shared.RegistrationScreenTitleSubtitle
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
@@ -264,8 +273,34 @@ private fun BackupAvailableContent(
|
||||
}
|
||||
|
||||
RegistrationScreen(
|
||||
title = stringResource(id = R.string.RemoteRestoreActivity__restore_from_backup),
|
||||
subtitle = subtitle,
|
||||
topContent = {
|
||||
if (state.backupTier != null) {
|
||||
RegistrationScreenTitleSubtitle(
|
||||
title = stringResource(id = R.string.RemoteRestoreActivity__restore_from_backup),
|
||||
subtitle = AnnotatedString(subtitle)
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_backup_24),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(color = SignalTheme.colors.colorSurface2, shape = CircleShape)
|
||||
.padding(12.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.RemoteRestoreActivity__restore_from_backup),
|
||||
style = MaterialTheme.typography.headlineMedium.copy(textAlign = TextAlign.Center),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
bottomContent = {
|
||||
Column {
|
||||
if (state.isLoaded()) {
|
||||
@@ -286,26 +321,48 @@ private fun BackupAvailableContent(
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = SignalTheme.colors.colorSurface2, shape = RoundedCornerShape(18.dp))
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(top = 20.dp, bottom = 18.dp)
|
||||
) {
|
||||
if (state.backupTier != null) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = SignalTheme.colors.colorSurface2, shape = RoundedCornerShape(18.dp))
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(top = 20.dp, bottom = 18.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.RemoteRestoreActivity__your_backup_includes),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 6.dp)
|
||||
)
|
||||
|
||||
getFeatures(state.backupTier, state.backupMediaTTL).forEach {
|
||||
MessageBackupsTypeFeatureRow(
|
||||
messageBackupsTypeFeature = it,
|
||||
iconTint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 6.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.RemoteRestoreActivity__your_backup_includes),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 6.dp)
|
||||
text = stringResource(R.string.RemoteRestoreActivity__if_you_choose_not_to_restore),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 20.dp)
|
||||
)
|
||||
|
||||
getFeatures(state.backupTier, state.backupMediaTTL).forEach {
|
||||
MessageBackupsTypeFeatureRow(
|
||||
messageBackupsTypeFeature = it,
|
||||
iconTint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 6.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.RemoteRestoreActivity__if_you_choose_not_to_restore),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
when (state.importState) {
|
||||
@@ -340,6 +397,23 @@ private fun RestoreFromBackupContentPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun RestoreFromBackupUnknownTierPreview() {
|
||||
Previews.Preview {
|
||||
RestoreFromBackupContent(
|
||||
state = RemoteRestoreViewModel.ScreenState(
|
||||
loadState = RemoteRestoreViewModel.ScreenState.LoadState.LOADED,
|
||||
backupTier = null,
|
||||
backupTime = System.currentTimeMillis(),
|
||||
backupSize = 0.bytes,
|
||||
importState = RemoteRestoreViewModel.ImportState.Restored,
|
||||
restoreProgress = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun RestoreFromBackupContentLoadingPreview() {
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.backup.v2.RemoteRestoreResult
|
||||
import org.thoughtcrime.securesms.backup.v2.RestoreTimestampResult
|
||||
import org.thoughtcrime.securesms.backup.v2.RestoreV2Event
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.keyvalue.Completed
|
||||
@@ -54,22 +55,28 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
fun reload() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
store.update { it.copy(loadState = ScreenState.LoadState.LOADING, loadAttempts = it.loadAttempts + 1) }
|
||||
val tier: MessageBackupTier? = BackupRepository.restoreBackupTier(SignalStore.account.requireAci())
|
||||
val result = BackupRepository.restoreBackupFileTimestamp()
|
||||
store.update {
|
||||
if (tier != null && SignalStore.backup.lastBackupTime > 0) {
|
||||
it.copy(
|
||||
loadState = ScreenState.LoadState.LOADED,
|
||||
backupTier = SignalStore.backup.backupTier,
|
||||
backupTime = SignalStore.backup.lastBackupTime,
|
||||
backupSize = SignalStore.registration.restoreBackupMediaSize.bytes
|
||||
)
|
||||
} else {
|
||||
if (SignalStore.backup.isBackupTierRestored || SignalStore.backup.lastBackupTime == 0L) {
|
||||
when (result) {
|
||||
is RestoreTimestampResult.Success -> {
|
||||
it.copy(
|
||||
loadState = ScreenState.LoadState.LOADED,
|
||||
backupTier = SignalStore.backup.backupTier,
|
||||
backupTime = SignalStore.backup.lastBackupTime,
|
||||
backupSize = SignalStore.registration.restoreBackupMediaSize.bytes
|
||||
)
|
||||
}
|
||||
|
||||
is RestoreTimestampResult.NotFound -> {
|
||||
it.copy(loadState = ScreenState.LoadState.NOT_FOUND)
|
||||
} else if (it.loadState == ScreenState.LoadState.LOADING) {
|
||||
it.copy(loadState = ScreenState.LoadState.FAILURE)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (it.loadState == ScreenState.LoadState.LOADING) {
|
||||
it.copy(loadState = ScreenState.LoadState.FAILURE)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalPreview
|
||||
import org.signal.core.ui.compose.horizontalGutters
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.GooglePlayBillingDependencies.context
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
||||
|
||||
private const val TAP_TARGET = 8
|
||||
@@ -62,7 +63,46 @@ fun RegistrationScreen(
|
||||
fun RegistrationScreen(
|
||||
title: String,
|
||||
subtitle: AnnotatedString?,
|
||||
bottomContent: @Composable (BoxScope.() -> Unit),
|
||||
bottomContent: @Composable BoxScope.() -> Unit,
|
||||
mainContent: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
RegistrationScreen(
|
||||
topContent = { RegistrationScreenTitleSubtitle(title, subtitle) },
|
||||
bottomContent = bottomContent,
|
||||
mainContent = mainContent
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RegistrationScreenTitleSubtitle(
|
||||
title: String,
|
||||
subtitle: AnnotatedString?
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (subtitle != null) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
}
|
||||
|
||||
/**
|
||||
* A base framework for rendering the various v3 registration screens.
|
||||
*/
|
||||
@Composable
|
||||
fun RegistrationScreen(
|
||||
topContent: @Composable ColumnScope.() -> Unit,
|
||||
bottomContent: @Composable BoxScope.() -> Unit,
|
||||
mainContent: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Surface {
|
||||
@@ -84,9 +124,7 @@ fun RegistrationScreen(
|
||||
.padding(top = 40.dp, bottom = 16.dp)
|
||||
.horizontalGutters()
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
@@ -102,19 +140,10 @@ fun RegistrationScreen(
|
||||
previousToast = Toast.makeText(context, context.resources.getQuantityString(R.plurals.RegistrationActivity_debug_log_hint, remaining, remaining), Toast.LENGTH_SHORT).apply { show() }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (subtitle != null) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
) {
|
||||
topContent()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
mainContent()
|
||||
}
|
||||
|
||||
@@ -151,3 +180,20 @@ private fun RegistrationScreenPreview() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun RegistrationScreenNoTitlePreview() {
|
||||
Previews.Preview {
|
||||
RegistrationScreen(
|
||||
topContent = { Text("Top content") },
|
||||
bottomContent = {
|
||||
TextButton(onClick = {}) {
|
||||
Text("Bottom Button")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Main content")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user