mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 10:20:25 +01:00
Do regv3 storage service restore flows right.
This commit is contained in:
committed by
Greyson Parrelli
parent
a31ed28b5f
commit
0b3a949264
@@ -168,10 +168,10 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
return STATE_UI_BLOCKING_UPGRADE;
|
||||
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||
return STATE_WELCOME_PUSH_SCREEN;
|
||||
} else if (SignalStore.storageService().getNeedsAccountRestore()) {
|
||||
return STATE_ENTER_SIGNAL_PIN;
|
||||
} else if (userCanTransferOrRestore()) {
|
||||
return STATE_TRANSFER_OR_RESTORE;
|
||||
} else if (SignalStore.storageService().getNeedsAccountRestore()) {
|
||||
return STATE_ENTER_SIGNAL_PIN;
|
||||
} else if (userMustSetProfileName()) {
|
||||
return STATE_CREATE_PROFILE_NAME;
|
||||
} else if (userMustCreateSignalPin()) {
|
||||
|
||||
@@ -1313,7 +1313,7 @@ object BackupRepository {
|
||||
val timestampResult = getBackupFileLastModified()
|
||||
when {
|
||||
timestampResult is NetworkResult.Success -> {
|
||||
timestampResult.result?.let { SignalStore.backup.lastBackupTime = it.toMillis() }
|
||||
SignalStore.backup.lastBackupTime = timestampResult.result?.toMillis() ?: 0L
|
||||
}
|
||||
|
||||
timestampResult is NetworkResult.StatusCodeError && timestampResult.code == 404 -> {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -9,6 +10,8 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
class RegistrationValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(RegistrationValues::class)
|
||||
|
||||
private const val REGISTRATION_COMPLETE = "registration.complete"
|
||||
private const val PIN_REQUIRED = "registration.pin_required"
|
||||
private const val HAS_UPLOADED_PROFILE = "registration.has_uploaded_profile"
|
||||
@@ -72,7 +75,17 @@ class RegistrationValues internal constructor(store: KeyValueStore) : SignalStor
|
||||
@get:JvmName("isRestoringOnNewDevice")
|
||||
var restoringOnNewDevice: Boolean by booleanValue(RESTORING_ON_NEW_DEVICE, false)
|
||||
|
||||
var restoreDecisionState: RestoreDecisionState by protoValue(RESTORE_DECISION_STATE, RestoreDecisionState.Skipped, RestoreDecisionState.ADAPTER) { newValue ->
|
||||
AppDependencies.incomingMessageObserver.notifyRegistrationStateChanged()
|
||||
}
|
||||
var restoreDecisionState: RestoreDecisionState
|
||||
get() = store.getBlob(RESTORE_DECISION_STATE, null)?.let { RestoreDecisionState.ADAPTER.decode(it) } ?: RestoreDecisionState.Skipped
|
||||
set(newValue) {
|
||||
if (isRegistrationComplete) {
|
||||
Log.w(TAG, "Registration was completed, cannot change initial restore decision state")
|
||||
} else {
|
||||
Log.v(TAG, "Restore decision set: $newValue", Throwable())
|
||||
store.beginWrite()
|
||||
.putBlob(RESTORE_DECISION_STATE, newValue.encode())
|
||||
.apply()
|
||||
AppDependencies.incomingMessageObserver.notifyRegistrationStateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,23 @@ package org.thoughtcrime.securesms.pin
|
||||
import android.app.backup.BackupManager
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.ReclaimUsernameAndLinkJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.ResetSvrGuessCountJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr3MirrorJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.StorageServiceRestore
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
|
||||
@@ -168,7 +167,6 @@ object SvrRepository {
|
||||
SignalStore.svr.isRegistrationLockEnabled = false
|
||||
SignalStore.pin.resetPinReminders()
|
||||
SignalStore.pin.keyboardType = pinKeyboardType
|
||||
SignalStore.storageService.needsAccountRestore = false
|
||||
|
||||
when (implementation.svrVersion) {
|
||||
SvrVersion.SVR2 -> SignalStore.svr.appendSvr2AuthTokenToList(response.authorization.asBasic())
|
||||
@@ -178,15 +176,8 @@ object SvrRepository {
|
||||
AppDependencies.jobManager.add(ResetSvrGuessCountJob())
|
||||
stopwatch.split("metadata")
|
||||
|
||||
AppDependencies.jobManager.runSynchronously(StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN)
|
||||
stopwatch.split("account-restore")
|
||||
|
||||
AppDependencies
|
||||
.jobManager
|
||||
.startChain(StorageSyncJob())
|
||||
.then(ReclaimUsernameAndLinkJob())
|
||||
.enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10))
|
||||
stopwatch.split("contact-restore")
|
||||
runBlocking { StorageServiceRestore.restore() }
|
||||
stopwatch.split("restore-account")
|
||||
|
||||
if (implementation.svrVersion != SvrVersion.SVR2 && Svr3Migration.shouldWriteToSvr2) {
|
||||
AppDependencies.jobManager.add(Svr2MirrorJob())
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class RegistrationUtil {
|
||||
SignalStore.account().isRegistered() &&
|
||||
!Recipient.self().getProfileName().isEmpty() &&
|
||||
(SignalStore.svr().hasOptedInWithAccess() || SignalStore.svr().hasOptedOut()) &&
|
||||
(!RemoteConfig.INSTANCE.restoreAfterRegistration() || RestoreDecisionStateUtil.isTerminal(SignalStore.registration().getRestoreDecisionState())))
|
||||
(!RemoteConfig.restoreAfterRegistration() || RestoreDecisionStateUtil.isTerminal(SignalStore.registration().getRestoreDecisionState())))
|
||||
{
|
||||
Log.i(TAG, "Marking registration completed.", new Throwable());
|
||||
SignalStore.registration().markRegistrationComplete();
|
||||
|
||||
@@ -17,16 +17,8 @@ import org.thoughtcrime.securesms.BaseActivity
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.isDecisionPending
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreActivity
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.sms.SmsRetrieverReceiver
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.RemoteRestoreActivity
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
/**
|
||||
* Activity to hold the entire registration process.
|
||||
@@ -69,40 +61,9 @@ class RegistrationActivity : BaseActivity() {
|
||||
SignalStore.misc.shouldShowLinkedDevicesReminder = sharedViewModel.isReregister
|
||||
}
|
||||
|
||||
if (SignalStore.storageService.needsAccountRestore) {
|
||||
Log.i(TAG, "Performing pin restore.")
|
||||
startActivity(Intent(this, PinRestoreActivity::class.java))
|
||||
finish()
|
||||
} else {
|
||||
val isProfileNameEmpty = Recipient.self().profileName.isEmpty
|
||||
val isAvatarEmpty = !AvatarHelper.hasAvatar(this, Recipient.self().id)
|
||||
val needsProfile = isProfileNameEmpty || isAvatarEmpty
|
||||
val needsPin = !SignalStore.svr.hasOptedInWithAccess()
|
||||
|
||||
Log.i(TAG, "Pin restore flow not required. Profile name empty: $isProfileNameEmpty | Profile avatar empty: $isAvatarEmpty | Needs PIN: $needsPin")
|
||||
|
||||
if (!needsProfile && !needsPin) {
|
||||
sharedViewModel.completeRegistration()
|
||||
}
|
||||
|
||||
val startIntent = MainActivity.clearTop(this)
|
||||
|
||||
val nextIntent: Intent? = when {
|
||||
needsPin -> CreateSvrPinActivity.getIntentForPinCreate(this@RegistrationActivity)
|
||||
SignalStore.registration.restoreDecisionState.isDecisionPending && RemoteConfig.messageBackups -> RemoteRestoreActivity.getIntent(this@RegistrationActivity)
|
||||
needsProfile -> CreateProfileActivity.getIntentForUserProfile(this@RegistrationActivity)
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (nextIntent != null) {
|
||||
startIntent.putExtra("next_intent", nextIntent)
|
||||
}
|
||||
|
||||
Log.d(TAG, "Launching ${startIntent.component} with next_intent: ${nextIntent?.component}")
|
||||
startActivity(startIntent)
|
||||
finish()
|
||||
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
|
||||
}
|
||||
startActivity(MainActivity.clearTop(this))
|
||||
finish()
|
||||
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
|
||||
}
|
||||
|
||||
private inner class SmsRetrieverObserver : DefaultLifecycleObserver {
|
||||
|
||||
@@ -75,4 +75,22 @@ data class RegistrationState(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toNavigationStateOnly(): NavigationState {
|
||||
return NavigationState(challengesRequested, challengesPresented, captchaToken, registrationCheckpoint, canSkipSms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of [RegistrationState] useful for deciding on navigation. Prevents other properties updating from re-triggering
|
||||
* navigation decisions.
|
||||
*/
|
||||
data class NavigationState(
|
||||
val challengesRequested: List<Challenge>,
|
||||
val challengesPresented: Set<Challenge>,
|
||||
val captchaToken: String? = null,
|
||||
val registrationCheckpoint: RegistrationCheckpoint,
|
||||
val canSkipSms: Boolean
|
||||
) {
|
||||
val challengesRemaining: List<Challenge> = challengesRequested.filterNot { it in challengesPresented }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.updateAndGet
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
@@ -32,9 +31,6 @@ 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.jobs.StorageAccountRestoreJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob
|
||||
import org.thoughtcrime.securesms.keyvalue.NewAccount
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
@@ -75,6 +71,7 @@ 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
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.dualsim.MccMncProducer
|
||||
@@ -85,7 +82,6 @@ import org.whispersystems.signalservice.api.svr.Svr3Credentials
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -880,37 +876,29 @@ class RegistrationViewModel : ViewModel() {
|
||||
RegistrationRepository.registerAccountLocally(context, metadata)
|
||||
|
||||
if (!remoteResult.storageCapable && SignalStore.registration.restoreDecisionState.isDecisionPending) {
|
||||
// Not being storage capable is a high signal that account is new and there's no data to restore
|
||||
Log.v(TAG, "Not storage capable and still pending restore decision, likely an account with no data to restore, skipping post register restore")
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.NewAccount
|
||||
}
|
||||
|
||||
if (reglockEnabled || SignalStore.svr.hasOptedInWithAccess()) {
|
||||
SignalStore.onboarding.clearAll()
|
||||
|
||||
if (!SignalStore.registration.restoreDecisionState.isDecisionPending) {
|
||||
Log.d(TAG, "No pending restore decisions, can restore account from storage service")
|
||||
StorageServiceRestore.restore()
|
||||
}
|
||||
}
|
||||
|
||||
if (reglockEnabled || SignalStore.svr.hasOptedInWithAccess()) {
|
||||
val stopwatch = Stopwatch("post-reg-storage-service")
|
||||
|
||||
AppDependencies.jobManager.runSynchronously(StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN)
|
||||
stopwatch.split("account-restore")
|
||||
|
||||
AppDependencies.jobManager
|
||||
.startChain(StorageSyncJob())
|
||||
.then(ReclaimUsernameAndLinkJob())
|
||||
.enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10))
|
||||
stopwatch.split("storage-sync")
|
||||
|
||||
if (SignalStore.account.restoredAccountEntropyPool) {
|
||||
Log.d(TAG, "Restoring backup tier")
|
||||
BackupRepository.restoreBackupTier(SignalStore.account.requireAci())
|
||||
stopwatch.split("backup-tier")
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
}
|
||||
|
||||
refreshRemoteConfig()
|
||||
|
||||
val checkpoint = if (SignalStore.registration.restoreDecisionState.isDecisionPending &&
|
||||
SignalStore.registration.restoreDecisionState.isWantingManualRemoteRestore &&
|
||||
!SignalStore.backup.isBackupTierRestored
|
||||
(!SignalStore.backup.isBackupTierRestored || SignalStore.backup.lastBackupTime == 0L)
|
||||
) {
|
||||
RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED
|
||||
} else {
|
||||
@@ -932,8 +920,11 @@ class RegistrationViewModel : ViewModel() {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.intendToRestore(hasOldDevice, fromRemote)
|
||||
}
|
||||
|
||||
fun skipRestoreAfterRegistration() {
|
||||
fun skipRestore() {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
|
||||
}
|
||||
|
||||
fun resumeNormalRegistration() {
|
||||
store.update {
|
||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.LOCAL_REGISTRATION_COMPLETE)
|
||||
}
|
||||
@@ -941,7 +932,7 @@ class RegistrationViewModel : ViewModel() {
|
||||
|
||||
fun restoreBackupTier() {
|
||||
store.update {
|
||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.SERVICE_REGISTRATION_COMPLETED)
|
||||
it.copy(inProgress = true, registrationCheckpoint = RegistrationCheckpoint.SERVICE_REGISTRATION_COMPLETED)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
@@ -949,7 +940,7 @@ class RegistrationViewModel : ViewModel() {
|
||||
val tierUnknown = BackupRepository.restoreBackupTier(SignalStore.account.requireAci()) == null
|
||||
delay(max(0L, 500L - (System.currentTimeMillis() - start)))
|
||||
|
||||
if (tierUnknown) {
|
||||
if (tierUnknown || SignalStore.backup.lastBackupTime == 0L) {
|
||||
store.update {
|
||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED)
|
||||
}
|
||||
|
||||
@@ -160,18 +160,24 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||
handleRegistrationErrorResponse(it)
|
||||
sharedViewModel.registerAccountErrorShown()
|
||||
}
|
||||
|
||||
if (sharedState.challengesRequested.contains(Challenge.CAPTCHA) && sharedState.captchaToken.isNotNullOrBlank()) {
|
||||
sharedViewModel.submitCaptchaToken(requireContext())
|
||||
} else if (sharedState.challengesRemaining.isNotEmpty()) {
|
||||
handleChallenges(sharedState.challengesRemaining)
|
||||
} else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED && sharedState.canSkipSms) {
|
||||
moveToEnterPinScreen()
|
||||
} else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED) {
|
||||
moveToVerificationEntryScreen()
|
||||
}
|
||||
}
|
||||
|
||||
sharedViewModel
|
||||
.uiState
|
||||
.map { it.toNavigationStateOnly() }
|
||||
.distinctUntilChanged()
|
||||
.observe(viewLifecycleOwner) { sharedState ->
|
||||
if (sharedState.challengesRequested.contains(Challenge.CAPTCHA) && sharedState.captchaToken.isNotNullOrBlank()) {
|
||||
sharedViewModel.submitCaptchaToken(requireContext())
|
||||
} else if (sharedState.challengesRemaining.isNotEmpty()) {
|
||||
handleChallenges(sharedState.challengesRemaining)
|
||||
} else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED && sharedState.canSkipSms) {
|
||||
moveToEnterPinScreen()
|
||||
} else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED) {
|
||||
moveToVerificationEntryScreen()
|
||||
}
|
||||
}
|
||||
|
||||
fragmentViewModel
|
||||
.uiState
|
||||
.map { it.phoneNumberRegionCode }
|
||||
|
||||
@@ -47,7 +47,7 @@ class EnterBackupKeyFragment : ComposeFragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
sharedViewModel
|
||||
.state
|
||||
.map { it.registerAccountError }
|
||||
@@ -60,7 +60,7 @@ class EnterBackupKeyFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
sharedViewModel
|
||||
.state
|
||||
.filter { it.registrationCheckpoint == RegistrationCheckpoint.BACKUP_TIER_NOT_RESTORED }
|
||||
@@ -94,18 +94,31 @@ class EnterBackupKeyFragment : ComposeFragment() {
|
||||
},
|
||||
|
||||
onLearnMore = { CommunicationActions.openBrowserLink(requireContext(), LEARN_MORE_URL) },
|
||||
onSkip = { findNavController().safeNavigate(EnterBackupKeyFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.RESTART_AFTER_COLLECTION)) }
|
||||
|
||||
) {
|
||||
ErrorContent(
|
||||
state = state,
|
||||
onBackupTierRetry = { sharedViewModel.restoreBackupTier() },
|
||||
onSkipRestoreAfterRegistration = sharedViewModel::skipRestoreAfterRegistration,
|
||||
onBackupTierNotRestoredDismiss = viewModel::hideRestoreBackupKeyFailed,
|
||||
onRegistrationErrorDismiss = viewModel::clearRegistrationError,
|
||||
onBackupKeyHelp = { CommunicationActions.openBrowserLink(requireContext(), LEARN_MORE_URL) }
|
||||
)
|
||||
}
|
||||
onSkip = {
|
||||
sharedViewModel.skipRestore()
|
||||
findNavController().safeNavigate(EnterBackupKeyFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.RESTART_AFTER_COLLECTION))
|
||||
},
|
||||
dialogContent = {
|
||||
if (state.showStorageAccountRestoreProgress) {
|
||||
Dialogs.IndeterminateProgressDialog()
|
||||
} else {
|
||||
ErrorContent(
|
||||
state = state,
|
||||
onBackupTierRetry = { sharedViewModel.restoreBackupTier() },
|
||||
onSkipRestoreAfterRegistration = {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
sharedViewModel.skipRestore()
|
||||
viewModel.performStorageServiceAccountRestoreIfNeeded()
|
||||
sharedViewModel.resumeNormalRegistration()
|
||||
}
|
||||
},
|
||||
onBackupTierNotRestoredDismiss = viewModel::hideRestoreBackupKeyFailed,
|
||||
onRegistrationErrorDismiss = viewModel::clearRegistrationError,
|
||||
onBackupKeyHelp = { CommunicationActions.openBrowserLink(requireContext(), LEARN_MORE_URL) }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ fun EnterBackupKeyScreen(
|
||||
onNextClicked: () -> Unit = {},
|
||||
onLearnMore: () -> Unit = {},
|
||||
onSkip: () -> Unit = {},
|
||||
errorContent: @Composable () -> Unit
|
||||
dialogContent: @Composable () -> Unit
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
@@ -185,7 +185,7 @@ fun EnterBackupKeyScreen(
|
||||
}
|
||||
}
|
||||
|
||||
errorContent()
|
||||
dialogContent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,13 @@ class EnterBackupKeyViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun performStorageServiceAccountRestoreIfNeeded() {
|
||||
if (SignalStore.account.restoredAccountEntropyPool || SignalStore.svr.masterKeyForInitialDataRestore != null) {
|
||||
store.update { it.copy(showBackupTierNotRestoreError = false, showStorageAccountRestoreProgress = true) }
|
||||
StorageServiceRestore.restore()
|
||||
}
|
||||
}
|
||||
|
||||
data class EnterBackupKeyState(
|
||||
val backupKeyValid: Boolean = false,
|
||||
val requiredLength: Int,
|
||||
@@ -109,6 +116,7 @@ class EnterBackupKeyViewModel : ViewModel() {
|
||||
val showRegistrationError: Boolean = false,
|
||||
val showBackupTierNotRestoreError: Boolean = false,
|
||||
val registerAccountResult: RegisterAccountResult? = null,
|
||||
val aepValidationError: AEPValidationError? = null
|
||||
val aepValidationError: AEPValidationError? = null,
|
||||
val showStorageAccountRestoreProgress: Boolean = false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFe
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFeatureRow
|
||||
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.shared.RegistrationScreen
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
@@ -93,7 +92,8 @@ class RemoteRestoreActivity : BaseActivity() {
|
||||
.firstOrNull()
|
||||
|
||||
if (restored != null) {
|
||||
continueRegistration(restored.missingProfileData)
|
||||
startActivity(MainActivity.clearTop(this@RemoteRestoreActivity))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +106,15 @@ class RemoteRestoreActivity : BaseActivity() {
|
||||
state = state,
|
||||
onRestoreBackupClick = { viewModel.restore() },
|
||||
onCancelClick = {
|
||||
if (state.isRemoteRestoreOnlyOption) {
|
||||
viewModel.skipRestore()
|
||||
startActivity(MainActivity.clearTop(this))
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
if (state.isRemoteRestoreOnlyOption) {
|
||||
viewModel.skipRestore()
|
||||
viewModel.performStorageServiceAccountRestoreIfNeeded()
|
||||
startActivity(MainActivity.clearTop(this@RemoteRestoreActivity))
|
||||
}
|
||||
|
||||
finish()
|
||||
finish()
|
||||
}
|
||||
},
|
||||
onErrorDialogDismiss = { viewModel.clearError() },
|
||||
onUpdateSignal = {
|
||||
@@ -129,20 +132,6 @@ class RemoteRestoreActivity : BaseActivity() {
|
||||
fun onEvent(restoreEvent: RestoreV2Event) {
|
||||
viewModel.updateRestoreProgress(restoreEvent)
|
||||
}
|
||||
|
||||
private fun continueRegistration(missingProfileData: Boolean) {
|
||||
val main = MainActivity.clearTop(this)
|
||||
|
||||
if (missingProfileData) {
|
||||
val profile = CreateProfileActivity.getIntentForUserProfile(this)
|
||||
profile.putExtra("next_intent", main)
|
||||
startActivity(profile)
|
||||
} else {
|
||||
startActivity(main)
|
||||
}
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -171,12 +160,16 @@ private fun RestoreFromBackupContent(
|
||||
}
|
||||
|
||||
RemoteRestoreViewModel.ScreenState.LoadState.NOT_FOUND -> {
|
||||
RestoreFailedDialog(onDismiss = onCancelClick)
|
||||
BackupNotFoundDialog(onDismiss = onCancelClick)
|
||||
}
|
||||
|
||||
RemoteRestoreViewModel.ScreenState.LoadState.FAILURE -> {
|
||||
RestoreFailedDialog(onDismiss = onCancelClick)
|
||||
}
|
||||
|
||||
RemoteRestoreViewModel.ScreenState.LoadState.STORAGE_SERVICE_RESTORE -> {
|
||||
Dialogs.IndeterminateProgressDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,6 +396,19 @@ private fun ProgressDialogPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackupNotFoundDialog(
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.EnterBackupKey_backup_not_found),
|
||||
body = stringResource(R.string.EnterBackupKey_backup_key_you_entered_is_correct_but_no_backup),
|
||||
confirm = stringResource(android.R.string.ok),
|
||||
onConfirm = onDismiss,
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RestoreFailedDialog(
|
||||
onDismiss: () -> Unit = {}
|
||||
|
||||
@@ -27,14 +27,11 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.BackupRestoreJob
|
||||
import org.thoughtcrime.securesms.jobs.BackupRestoreMediaJob
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.SyncArchivedMediaJob
|
||||
import org.thoughtcrime.securesms.keyvalue.Completed
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registrationv3.data.QuickRegistrationRepository
|
||||
import org.thoughtcrime.securesms.registrationv3.data.RegistrationRepository
|
||||
import org.whispersystems.signalservice.api.registration.RestoreMethod
|
||||
|
||||
class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
@@ -58,7 +55,7 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val tier: MessageBackupTier? = BackupRepository.restoreBackupTier(SignalStore.account.requireAci())
|
||||
store.update {
|
||||
if (tier != null) {
|
||||
if (tier != null && SignalStore.backup.lastBackupTime > 0) {
|
||||
it.copy(
|
||||
loadState = ScreenState.LoadState.LOADED,
|
||||
backupTier = SignalStore.backup.backupTier,
|
||||
@@ -66,7 +63,7 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
backupSize = SignalStore.backup.totalBackupSize.bytes
|
||||
)
|
||||
} else {
|
||||
if (SignalStore.backup.isBackupTierRestored) {
|
||||
if (SignalStore.backup.isBackupTierRestored || SignalStore.backup.lastBackupTime == 0L) {
|
||||
it.copy(loadState = ScreenState.LoadState.NOT_FOUND)
|
||||
} else if (it.loadState == ScreenState.LoadState.LOADING) {
|
||||
it.copy(loadState = ScreenState.LoadState.FAILURE)
|
||||
@@ -108,12 +105,9 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
Log.i(TAG, "Restore successful")
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Completed
|
||||
|
||||
if (!RegistrationRepository.isMissingProfileData()) {
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
AppDependencies.jobManager.add(ProfileUploadJob())
|
||||
}
|
||||
StorageServiceRestore.restore()
|
||||
|
||||
store.update { it.copy(importState = ImportState.Restored(RegistrationRepository.isMissingProfileData())) }
|
||||
store.update { it.copy(importState = ImportState.Restored) }
|
||||
}
|
||||
|
||||
JobTracker.JobState.PENDING,
|
||||
@@ -155,6 +149,13 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun performStorageServiceAccountRestoreIfNeeded() {
|
||||
if (SignalStore.account.restoredAccountEntropyPool || SignalStore.svr.masterKeyForInitialDataRestore != null) {
|
||||
store.update { it.copy(loadState = ScreenState.LoadState.STORAGE_SERVICE_RESTORE) }
|
||||
StorageServiceRestore.restore()
|
||||
}
|
||||
}
|
||||
|
||||
data class ScreenState(
|
||||
val isRemoteRestoreOnlyOption: Boolean = false,
|
||||
val backupTier: MessageBackupTier? = null,
|
||||
@@ -170,14 +171,14 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
}
|
||||
|
||||
enum class LoadState {
|
||||
LOADING, LOADED, NOT_FOUND, FAILURE
|
||||
LOADING, LOADED, NOT_FOUND, FAILURE, STORAGE_SERVICE_RESTORE
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ImportState {
|
||||
data object None : ImportState
|
||||
data object InProgress : ImportState
|
||||
data class Restored(val missingProfileData: Boolean) : ImportState
|
||||
data object Restored : ImportState
|
||||
data object Failed : ImportState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class SelectManualRestoreMethodFragment : ComposeFragment() {
|
||||
|
||||
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
||||
|
||||
private val launchRestoreActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
private val localBackupRestore = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
when (val resultCode = result.resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
sharedViewModel.onBackupSuccessfullyRestored()
|
||||
@@ -47,7 +47,10 @@ class SelectManualRestoreMethodFragment : ComposeFragment() {
|
||||
SelectRestoreMethodScreen(
|
||||
restoreMethods = listOf(RestoreMethod.FROM_SIGNAL_BACKUPS, RestoreMethod.FROM_LOCAL_BACKUP_V1),
|
||||
onRestoreMethodClicked = this::startRestoreMethod,
|
||||
onSkip = { findNavController().safeNavigate(SelectManualRestoreMethodFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.NORMAL)) }
|
||||
onSkip = {
|
||||
sharedViewModel.skipRestore()
|
||||
findNavController().safeNavigate(SelectManualRestoreMethodFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.NORMAL))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ class SelectManualRestoreMethodFragment : ComposeFragment() {
|
||||
}
|
||||
RestoreMethod.FROM_LOCAL_BACKUP_V1 -> {
|
||||
sharedViewModel.intendToRestore(hasOldDevice = false, fromRemote = false)
|
||||
launchRestoreActivity.launch(RestoreActivity.getLocalRestoreIntent(requireContext()))
|
||||
localBackupRestore.launch(RestoreActivity.getLocalRestoreIntent(requireContext()))
|
||||
}
|
||||
RestoreMethod.FROM_OLD_DEVICE -> error("Device transfer not supported in manual restore flow")
|
||||
RestoreMethod.FROM_LOCAL_BACKUP_V2 -> error("Not currently supported")
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.registrationv3.ui.restore
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -24,7 +25,8 @@ import org.thoughtcrime.securesms.registrationv3.ui.shared.RegistrationScreen
|
||||
fun SelectRestoreMethodScreen(
|
||||
restoreMethods: List<RestoreMethod>,
|
||||
onRestoreMethodClicked: (RestoreMethod) -> Unit = {},
|
||||
onSkip: () -> Unit = {}
|
||||
onSkip: () -> Unit = {},
|
||||
extraContent: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
RegistrationScreen(
|
||||
title = stringResource(id = R.string.SelectRestoreMethodFragment__restore_or_transfer_account),
|
||||
@@ -46,6 +48,8 @@ fun SelectRestoreMethodScreen(
|
||||
onRowClick = { onRestoreMethodClicked(method) }
|
||||
)
|
||||
}
|
||||
|
||||
extraContent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registrationv3.ui.restore
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.enqueueBlocking
|
||||
import org.thoughtcrime.securesms.jobmanager.runJobBlocking
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.ReclaimUsernameAndLinkJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registrationv3.data.RegistrationRepository
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object StorageServiceRestore {
|
||||
private val TAG = Log.tag(StorageServiceRestore::class)
|
||||
|
||||
/**
|
||||
* Restore account data from Storage Service in a quasi-blocking manner. Uses existing jobs
|
||||
* to perform the restore but will not wait indefinitely for them to finish so may return prior
|
||||
* to completing the restore.
|
||||
*/
|
||||
suspend fun restore() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val stopwatch = Stopwatch("storage-service-restore")
|
||||
|
||||
SignalStore.storageService.needsAccountRestore = false
|
||||
|
||||
AppDependencies.jobManager.runJobBlocking(StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN.milliseconds)
|
||||
stopwatch.split("account-restore")
|
||||
|
||||
AppDependencies
|
||||
.jobManager
|
||||
.startChain(StorageSyncJob())
|
||||
.then(ReclaimUsernameAndLinkJob())
|
||||
.enqueueBlocking(10.seconds)
|
||||
stopwatch.split("storage-sync-restore")
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
|
||||
val isMissingProfileData = RegistrationRepository.isMissingProfileData()
|
||||
|
||||
if (!isMissingProfileData) {
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
AppDependencies.jobManager.add(ProfileUploadJob())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -49,7 +50,7 @@ fun RegistrationScreen(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
bottomContent: @Composable (BoxScope.() -> Unit),
|
||||
mainContent: @Composable () -> Unit
|
||||
mainContent: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
RegistrationScreen(title, AnnotatedString(subtitle), bottomContent, mainContent)
|
||||
}
|
||||
@@ -62,7 +63,7 @@ fun RegistrationScreen(
|
||||
title: String,
|
||||
subtitle: AnnotatedString?,
|
||||
bottomContent: @Composable (BoxScope.() -> Unit),
|
||||
mainContent: @Composable () -> Unit
|
||||
mainContent: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.registrationv3.ui.phonenumber.EnterPhoneNumber
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
* First screen that is displayed on the very first app launch.
|
||||
@@ -49,6 +50,7 @@ class WelcomeFragment : LoggingFragment(R.layout.fragment_registration_welcome_v
|
||||
binding.welcomeContinueButton.setOnClickListener { onContinueClicked() }
|
||||
binding.welcomeTermsButton.setOnClickListener { onTermsClicked() }
|
||||
binding.welcomeTransferOrRestore.setOnClickListener { onRestoreOrTransferClicked() }
|
||||
binding.welcomeTransferOrRestore.visible = !sharedViewModel.isReregister
|
||||
|
||||
childFragmentManager.setFragmentResultListener(RestoreWelcomeBottomSheet.REQUEST_KEY, viewLifecycleOwner) { requestKey, bundle ->
|
||||
if (requestKey == RestoreWelcomeBottomSheet.REQUEST_KEY) {
|
||||
|
||||
@@ -7,15 +7,25 @@ package org.thoughtcrime.securesms.restore
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
import org.thoughtcrime.securesms.keyvalue.skippedRestoreChoice
|
||||
import org.thoughtcrime.securesms.registrationv3.data.QuickRegistrationRepository
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.RestoreMethod
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.StorageServiceRestore
|
||||
import org.thoughtcrime.securesms.restore.transferorrestore.BackupRestorationType
|
||||
import org.whispersystems.signalservice.api.registration.RestoreMethod as ApiRestoreMethod
|
||||
|
||||
/**
|
||||
* Shared view model for the restore flow.
|
||||
@@ -24,6 +34,9 @@ class RestoreViewModel : ViewModel() {
|
||||
private val store = MutableStateFlow(RestoreState())
|
||||
val uiState = store.asLiveData()
|
||||
|
||||
var showStorageAccountRestoreProgress by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
fun setNextIntent(nextIntent: Intent) {
|
||||
store.update {
|
||||
it.copy(nextIntent = nextIntent)
|
||||
@@ -84,4 +97,19 @@ class RestoreViewModel : ViewModel() {
|
||||
fun hasRestoredAccountEntropyPool(): Boolean {
|
||||
return SignalStore.account.restoredAccountEntropyPool
|
||||
}
|
||||
|
||||
fun skipRestore() {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
|
||||
|
||||
viewModelScope.launch {
|
||||
QuickRegistrationRepository.setRestoreMethodForOldDevice(ApiRestoreMethod.DECLINE)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun performStorageServiceAccountRestoreIfNeeded() {
|
||||
if (hasRestoredAccountEntropyPool() || SignalStore.svr.masterKeyForInitialDataRestore != null) {
|
||||
showStorageAccountRestoreProgress = true
|
||||
StorageServiceRestore.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.EnterBackupKeyScreen
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.RemoteRestoreActivity
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ class PostRegistrationEnterBackupKeyFragment : ComposeFragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
val successful = viewModel
|
||||
.state
|
||||
.map { it.restoreBackupTierSuccessful }
|
||||
@@ -52,8 +52,8 @@ class PostRegistrationEnterBackupKeyFragment : ComposeFragment() {
|
||||
.firstOrNull() ?: false
|
||||
|
||||
if (successful) {
|
||||
Log.i(TAG, "Successfully restored AEP, moving to remote restore")
|
||||
startActivity(RemoteRestoreActivity.getIntent(requireContext()))
|
||||
Log.i(TAG, "Successfully restored an AEP, moving to remote restore")
|
||||
findNavController().safeNavigate(PostRegistrationEnterBackupKeyFragmentDirections.goToRemoteRestoreActivity())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class PostRegistrationEnterBackupKeyFragment : ComposeFragment() {
|
||||
) {
|
||||
ErrorContent(
|
||||
showBackupTierNotRestoreError = state.showBackupTierNotRestoreError,
|
||||
onBackupTierRetry = { /*viewModel.restoreBackupTier()*/ }, // TODO
|
||||
onBackupTierRetry = { viewModel.restoreBackupTier() },
|
||||
onCancel = { findNavController().popBackStack() },
|
||||
onBackupTierNotRestoredDismiss = viewModel::hideRestoreBackupTierFailed
|
||||
)
|
||||
|
||||
@@ -18,9 +18,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.AccountEntropyPoolVerification
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.AccountEntropyPoolVerification.AEPValidationError
|
||||
@@ -70,9 +67,6 @@ class PostRegistrationEnterBackupKeyViewModel : ViewModel() {
|
||||
if (backupTier != null) {
|
||||
Log.i(TAG, "Backup tier found with entered AEP, migrating to new AEP and moving on to restore")
|
||||
SignalStore.account.restoreAccountEntropyPool(aep!!)
|
||||
AppDependencies.jobManager.add(Svr2MirrorJob())
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
|
||||
store.update { it.copy(restoreBackupTierSuccessful = true) }
|
||||
} else {
|
||||
Log.w(TAG, "Unable to validate AEP against currently registered account")
|
||||
|
||||
@@ -9,12 +9,13 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.ui.Dialogs
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
import org.thoughtcrime.securesms.registrationv3.data.QuickRegistrationRepository
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.RemoteRestoreActivity
|
||||
import org.thoughtcrime.securesms.registrationv3.ui.restore.RestoreMethod
|
||||
@@ -24,7 +25,7 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.whispersystems.signalservice.api.registration.RestoreMethod as ApiRestoreMethod
|
||||
|
||||
/**
|
||||
* Provide options to select restore/transfer operation and during quick/post registration.
|
||||
* Provide options to select restore/transfer operation during quick/post registration.
|
||||
*/
|
||||
class SelectRestoreMethodFragment : ComposeFragment() {
|
||||
|
||||
@@ -36,16 +37,23 @@ class SelectRestoreMethodFragment : ComposeFragment() {
|
||||
restoreMethods = viewModel.getAvailableRestoreMethods(),
|
||||
onRestoreMethodClicked = this::startRestoreMethod,
|
||||
onSkip = {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.skipRestore()
|
||||
viewModel.performStorageServiceAccountRestoreIfNeeded()
|
||||
|
||||
lifecycleScope.launch {
|
||||
QuickRegistrationRepository.setRestoreMethodForOldDevice(ApiRestoreMethod.DECLINE)
|
||||
if (isActive) {
|
||||
withContext(Dispatchers.Main) {
|
||||
startActivity(MainActivity.clearTop(requireContext()))
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(MainActivity.clearTop(requireContext()))
|
||||
activity?.finish()
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (viewModel.showStorageAccountRestoreProgress) {
|
||||
Dialogs.IndeterminateProgressDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRestoreMethod(method: RestoreMethod) {
|
||||
|
||||
Reference in New Issue
Block a user