diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index 282e7b1a3b..d979c19ae7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index f42ba63140..e30942f6f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -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 -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.kt index 3148d6dc95..a7d6d10ccb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.kt @@ -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() + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt index 09478278e8..86b85d6980 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt @@ -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()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java index fcc99addf3..cc8219ba7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationActivity.kt index f25eaacaf2..327b0fc7e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationActivity.kt @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationState.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationState.kt index 162e5cbd08..67cfbd5856 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationState.kt @@ -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, + val challengesPresented: Set, + val captchaToken: String? = null, + val registrationCheckpoint: RegistrationCheckpoint, + val canSkipSms: Boolean + ) { + val challengesRemaining: List = challengesRequested.filterNot { it in challengesPresented } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt index ab9ea41c40..7c9f9ef78c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/RegistrationViewModel.kt @@ -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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt index cd7d40ee22..07cfce10e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/phonenumber/EnterPhoneNumberFragment.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyFragment.kt index 883e5f41b0..e02d9c5f7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyFragment.kt @@ -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) } + ) + } + } + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyScreen.kt index 998d1ec156..a2ee6e1dcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyScreen.kt @@ -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() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyViewModel.kt index e1e619e41d..7dac3d8cd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/EnterBackupKeyViewModel.kt @@ -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 ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreActivity.kt index 212660a849..7e5575e3e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreActivity.kt @@ -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 = {} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreViewModel.kt index da9ff258b0..8f94417b7c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/RemoteRestoreViewModel.kt @@ -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 } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectManualRestoreMethodFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectManualRestoreMethodFragment.kt index 34721301bb..026c649fd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectManualRestoreMethodFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectManualRestoreMethodFragment.kt @@ -29,7 +29,7 @@ class SelectManualRestoreMethodFragment : ComposeFragment() { private val sharedViewModel by activityViewModels() - 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") diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectRestoreMethodScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectRestoreMethodScreen.kt index 31c550bbc9..5b31c3b5e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectRestoreMethodScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/SelectRestoreMethodScreen.kt @@ -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, 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() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/StorageServiceRestore.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/StorageServiceRestore.kt new file mode 100644 index 0000000000..1ae8d29f13 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/restore/StorageServiceRestore.kt @@ -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()) + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/shared/RegistrationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/shared/RegistrationScreen.kt index 74d5cd9623..8d82be9492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/shared/RegistrationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/shared/RegistrationScreen.kt @@ -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( diff --git a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/welcome/WelcomeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/welcome/WelcomeFragment.kt index 9634ce51e2..ad80756eb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/welcome/WelcomeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registrationv3/ui/welcome/WelcomeFragment.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/RestoreViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/RestoreViewModel.kt index 5300ffddd8..2a2009f37c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/RestoreViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/RestoreViewModel.kt @@ -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() + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyFragment.kt index 7fdf727634..a6db63ae58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyFragment.kt @@ -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 ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyViewModel.kt index 02e429229e..d719818ced 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/enterbackupkey/PostRegistrationEnterBackupKeyViewModel.kt @@ -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") diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/selection/SelectRestoreMethodFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/selection/SelectRestoreMethodFragment.kt index 2c0ae3090c..85b595b6cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/selection/SelectRestoreMethodFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/selection/SelectRestoreMethodFragment.kt @@ -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) { diff --git a/app/src/main/res/navigation/restore.xml b/app/src/main/res/navigation/restore.xml index aaeac87fe8..5e336b9ee4 100644 --- a/app/src/main/res/navigation/restore.xml +++ b/app/src/main/res/navigation/restore.xml @@ -93,8 +93,16 @@ android:id="@+id/enterBackupKey" android:name="org.thoughtcrime.securesms.restore.enterbackupkey.PostRegistrationEnterBackupKeyFragment"> + + +