Update registration for new restore flows.

This commit is contained in:
Cody Henthorne
2024-11-07 10:42:54 -05:00
committed by Greyson Parrelli
parent aad2624bd5
commit 22c4e2d084
140 changed files with 8364 additions and 2679 deletions

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.registration.data
import org.whispersystems.signalservice.api.account.PreKeyCollection
import org.whispersystems.signalservice.api.kbs.MasterKey
data class AccountRegistrationResult(
val uuid: String,
val pni: String,
val storageCapable: Boolean,
val number: String,
val masterKey: MasterKey?,
val pin: String?,
val aciPreKeyCollection: PreKeyCollection,
val pniPreKeyCollection: PreKeyCollection
)

View File

@@ -17,7 +17,7 @@ import org.whispersystems.signalservice.api.account.PreKeyCollection
* and combines them into a proto-backed class [LocalRegistrationMetadata] so they can be serialized & stored.
*/
object LocalRegistrationMetadataUtil {
fun createLocalRegistrationMetadata(localAciIdentityKeyPair: IdentityKeyPair, localPniIdentityKeyPair: IdentityKeyPair, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean): LocalRegistrationMetadata {
fun createLocalRegistrationMetadata(localAciIdentityKeyPair: IdentityKeyPair, localPniIdentityKeyPair: IdentityKeyPair, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean): LocalRegistrationMetadata {
return LocalRegistrationMetadata.Builder().apply {
aciIdentityKeyPair = localAciIdentityKeyPair.serialize().toByteString()
aciSignedPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString()

View File

@@ -622,15 +622,4 @@ object RegistrationRepository {
latch.countDown()
}
}
data class AccountRegistrationResult(
val uuid: String,
val pni: String,
val storageCapable: Boolean,
val number: String,
val masterKey: MasterKey?,
val pin: String?,
val aciPreKeyCollection: PreKeyCollection,
val pniPreKeyCollection: PreKeyCollection
)
}

View File

@@ -6,7 +6,7 @@
package org.thoughtcrime.securesms.registration.data.network
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.data.AccountRegistrationResult
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.SvrNoDataException
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException
@@ -23,7 +23,7 @@ import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
*/
sealed class RegisterAccountResult(cause: Throwable?) : RegistrationResult(cause) {
companion object {
fun from(networkResult: NetworkResult<RegistrationRepository.AccountRegistrationResult>): RegisterAccountResult {
fun from(networkResult: NetworkResult<AccountRegistrationResult>): RegisterAccountResult {
return when (networkResult) {
is NetworkResult.Success -> Success(networkResult.result)
is NetworkResult.ApplicationError -> UnknownError(networkResult.throwable)
@@ -55,7 +55,7 @@ sealed class RegisterAccountResult(cause: Throwable?) : RegistrationResult(cause
}
}
}
class Success(val accountRegistrationResult: RegistrationRepository.AccountRegistrationResult) : RegisterAccountResult(null)
class Success(val accountRegistrationResult: AccountRegistrationResult) : RegisterAccountResult(null)
class IncorrectRecoveryPassword(cause: Throwable) : RegisterAccountResult(cause)
class AuthorizationFailed(cause: Throwable) : RegisterAccountResult(cause)
class MalformedRequest(cause: Throwable) : RegisterAccountResult(cause)

View File

@@ -1,124 +0,0 @@
package org.thoughtcrime.securesms.registration.secondary
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.protocol.kdf.HKDF
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher
import org.whispersystems.signalservice.internal.push.ProvisionEnvelope
import org.whispersystems.signalservice.internal.push.ProvisionMessage
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.UUID
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Used to decrypt a secondary/link device provisioning message from the primary device.
*/
class SecondaryProvisioningCipher private constructor(private val secondaryIdentityKeyPair: IdentityKeyPair) {
val secondaryDevicePublicKey: IdentityKey = secondaryIdentityKeyPair.publicKey
fun decrypt(envelope: ProvisionEnvelope): ProvisionDecryptResult {
val primaryEphemeralPublicKey = envelope.publicKey!!.toByteArray()
val body = envelope.body!!.toByteArray()
val provisionMessageLength = body.size - VERSION_LENGTH - IV_LENGTH - MAC_LENGTH
if (provisionMessageLength <= 0) {
return ProvisionDecryptResult.Error
}
val version = body[0].toInt()
if (version != 1) {
return ProvisionDecryptResult.Error
}
val iv = body.sliceArray(1 until (1 + IV_LENGTH))
val theirMac = body.sliceArray(body.size - MAC_LENGTH until body.size)
val message = body.sliceArray(0 until body.size - MAC_LENGTH)
val cipherText = body.sliceArray((1 + IV_LENGTH) until body.size - MAC_LENGTH)
val sharedSecret = Curve.calculateAgreement(ECPublicKey(primaryEphemeralPublicKey), secondaryIdentityKeyPair.privateKey)
val derivedSecret: ByteArray = HKDF.deriveSecrets(sharedSecret, PrimaryProvisioningCipher.PROVISIONING_MESSAGE.toByteArray(), 64)
val cipherKey = derivedSecret.sliceArray(0 until 32)
val macKey = derivedSecret.sliceArray(32 until 64)
val ourHmac = getMac(macKey, message)
if (!MessageDigest.isEqual(theirMac, ourHmac)) {
return ProvisionDecryptResult.Error
}
val plaintext = try {
getPlaintext(cipherKey, iv, cipherText)
} catch (e: Exception) {
return ProvisionDecryptResult.Error
}
val provisioningMessage = ProvisionMessage.ADAPTER.decode(plaintext)
return ProvisionDecryptResult.Success(
uuid = UuidUtil.parseOrThrow(provisioningMessage.aci),
e164 = provisioningMessage.number!!,
identityKeyPair = IdentityKeyPair(IdentityKey(provisioningMessage.aciIdentityKeyPublic!!.toByteArray()), Curve.decodePrivatePoint(provisioningMessage.aciIdentityKeyPrivate!!.toByteArray())),
profileKey = ProfileKey(provisioningMessage.profileKey!!.toByteArray()),
areReadReceiptsEnabled = provisioningMessage.readReceipts == true,
primaryUserAgent = provisioningMessage.userAgent,
provisioningCode = provisioningMessage.provisioningCode!!,
provisioningVersion = provisioningMessage.provisioningVersion!!
)
}
private fun getMac(key: ByteArray, message: ByteArray): ByteArray? {
return try {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(key, "HmacSHA256"))
mac.doFinal(message)
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
} catch (e: InvalidKeyException) {
throw AssertionError(e)
}
}
private fun getPlaintext(key: ByteArray, iv: ByteArray, message: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
return cipher.doFinal(message)
}
companion object {
private const val VERSION_LENGTH = 1
private const val IV_LENGTH = 16
private const val MAC_LENGTH = 32
fun generate(): SecondaryProvisioningCipher {
return SecondaryProvisioningCipher(IdentityKeyUtil.generateIdentityKeyPair())
}
}
sealed class ProvisionDecryptResult {
object Error : ProvisionDecryptResult()
data class Success(
val uuid: UUID,
val e164: String,
val identityKeyPair: IdentityKeyPair,
val profileKey: ProfileKey,
val areReadReceiptsEnabled: Boolean,
val primaryUserAgent: String?,
val provisioningCode: String,
val provisioningVersion: Int
) : ProvisionDecryptResult()
}
}

View File

@@ -23,7 +23,7 @@ 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.registration.ui.restore.RemoteRestoreActivity
import org.thoughtcrime.securesms.registrationv3.ui.restore.RemoteRestoreActivity
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.RemoteConfig
@@ -119,7 +119,7 @@ class RegistrationActivity : BaseActivity() {
@JvmStatic
fun newIntentForNewRegistration(context: Context, originalIntent: Intent): Intent {
return Intent(context, RegistrationActivity::class.java).apply {
return Intent(context, getRegistrationClass()).apply {
putExtra(RE_REGISTRATION_EXTRA, false)
setData(originalIntent.data)
}
@@ -127,9 +127,13 @@ class RegistrationActivity : BaseActivity() {
@JvmStatic
fun newIntentForReRegistration(context: Context): Intent {
return Intent(context, RegistrationActivity::class.java).apply {
return Intent(context, getRegistrationClass()).apply {
putExtra(RE_REGISTRATION_EXTRA, true)
}
}
private fun getRegistrationClass(): Class<*> {
return if (RemoteConfig.restoreAfterRegistration) org.thoughtcrime.securesms.registrationv3.ui.RegistrationActivity::class.java else RegistrationActivity::class.java
}
}
}

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.pin.SvrRepository
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.registration.data.AccountRegistrationResult
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil
import org.thoughtcrime.securesms.registration.data.RegistrationData
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
@@ -827,7 +828,7 @@ class RegistrationViewModel : ViewModel() {
handleRegistrationResult(context, registrationData, registrationResponse, false)
}
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) {
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean) {
Log.v(TAG, "onSuccessfulRegistration()")
val metadata = LocalRegistrationMetadataUtil.createLocalRegistrationMetadata(SignalStore.account.aciIdentityKey, SignalStore.account.pniIdentityKey, registrationData, remoteResult, reglockEnabled)
RegistrationRepository.registerAccountLocally(context, metadata)

View File

@@ -104,7 +104,7 @@ class GrantPermissionsFragment : ComposeFragment() {
when (welcomeAction) {
WelcomeAction.CONTINUE -> findNavController().safeNavigate(GrantPermissionsFragmentDirections.actionEnterPhoneNumber())
WelcomeAction.RESTORE_BACKUP -> {
val restoreIntent = RestoreActivity.getIntentForTransferOrRestore(requireActivity())
val restoreIntent = RestoreActivity.getRestoreIntent(requireActivity())
launchRestoreActivity.launch(restoreIntent)
}
}

View File

@@ -1,380 +0,0 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.registration.ui.restore
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.signal.core.ui.Buttons
import org.signal.core.ui.Previews
import org.signal.core.ui.theme.SignalTheme
import org.thoughtcrime.securesms.BaseActivity
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.backup.v2.RestoreV2Event
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFeature
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFeatureRow
import org.thoughtcrime.securesms.backup.v2.ui.subscription.RemoteRestoreViewModel
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
import org.thoughtcrime.securesms.restore.transferorrestore.TransferOrRestoreMoreOptionsDialog
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.Util
import java.util.Locale
import org.signal.core.ui.R as CoreUiR
class RemoteRestoreActivity : BaseActivity() {
companion object {
fun getIntent(context: Context): Intent {
return Intent(context, RemoteRestoreActivity::class.java)
}
}
private val viewModel: RemoteRestoreViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val state by viewModel.state
SignalTheme {
Surface {
RestoreFromBackupContent(
features = getFeatureList(state.backupTier),
onRestoreBackupClick = {
viewModel.restore()
},
onCancelClick = {
finish()
},
onMoreOptionsClick = {
TransferOrRestoreMoreOptionsDialog.show(fragmentManager = supportFragmentManager, skipOnly = false)
},
state.backupTier,
state.backupTime,
state.backupTier != MessageBackupTier.PAID
)
if (state.importState == RemoteRestoreViewModel.ImportState.RESTORED) {
SideEffect {
SignalStore.registration.markRestoreCompleted()
RegistrationUtil.maybeMarkRegistrationComplete()
AppDependencies.jobManager.add(ProfileUploadJob())
startActivity(MainActivity.clearTop(this))
}
} else if (state.importState == RemoteRestoreViewModel.ImportState.IN_PROGRESS) {
ProgressDialog(state.restoreProgress)
}
}
}
}
EventBus.getDefault().registerForLifecycle(subscriber = this, lifecycleOwner = this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(restoreEvent: RestoreV2Event) {
viewModel.updateRestoreProgress(restoreEvent)
}
@Composable
private fun getFeatureList(tier: MessageBackupTier?): ImmutableList<MessageBackupsTypeFeature> {
return when (tier) {
null -> persistentListOf()
MessageBackupTier.PAID -> {
persistentListOf(
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
label = stringResource(id = R.string.RemoteRestoreActivity__all_of_your_media)
),
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_recent_compact_bold_16,
label = stringResource(id = R.string.RemoteRestoreActivity__all_of_your_messages)
)
)
}
MessageBackupTier.FREE -> {
persistentListOf(
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
label = stringResource(id = R.string.RemoteRestoreActivity__your_last_d_days_of_media, 30)
),
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_recent_compact_bold_16,
label = stringResource(id = R.string.RemoteRestoreActivity__all_of_your_messages)
)
)
}
}
}
/**
* A dialog that *just* shows a spinner. Useful for short actions where you need to
* let the user know that some action is completing.
*/
@Composable
fun ProgressDialog(restoreProgress: RestoreV2Event?) {
androidx.compose.material3.AlertDialog(
onDismissRequest = {},
confirmButton = {},
dismissButton = {},
text = {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.wrapContentSize()
) {
if (restoreProgress == null) {
CircularProgressIndicator(
modifier = Modifier
.padding(top = 55.dp, bottom = 16.dp)
.width(48.dp)
.height(48.dp)
)
} else {
CircularProgressIndicator(
progress = restoreProgress.getProgress(),
modifier = Modifier
.padding(top = 55.dp, bottom = 16.dp)
.width(48.dp)
.height(48.dp)
)
}
val progressText = when (restoreProgress?.type) {
RestoreV2Event.Type.PROGRESS_DOWNLOAD -> stringResource(id = R.string.RemoteRestoreActivity__downloading_backup)
RestoreV2Event.Type.PROGRESS_RESTORE -> stringResource(id = R.string.RemoteRestoreActivity__downloading_backup)
else -> stringResource(id = R.string.RemoteRestoreActivity__restoring)
}
Text(
text = progressText,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 12.dp)
)
if (restoreProgress != null) {
val progressBytes = Util.getPrettyFileSize(restoreProgress.count)
val totalBytes = Util.getPrettyFileSize(restoreProgress.estimatedTotalCount)
Text(
text = stringResource(id = R.string.RemoteRestoreActivity__s_of_s_s, progressBytes, totalBytes, "%.2f%%".format(restoreProgress.getProgress())),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(bottom = 12.dp)
)
}
}
}
},
modifier = Modifier.width(212.dp)
)
}
@Preview
@Composable
private fun ProgressDialogPreview() {
Previews.Preview {
ProgressDialog(RestoreV2Event(RestoreV2Event.Type.PROGRESS_RESTORE, 10, 1000))
}
}
@Preview
@Composable
private fun RestoreFromBackupContentPreview() {
Previews.Preview {
RestoreFromBackupContent(
features = persistentListOf(
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
label = "Your last 30 days of media"
),
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_recent_compact_bold_16,
label = "All of your text messages"
)
),
onRestoreBackupClick = {},
onCancelClick = {},
onMoreOptionsClick = {},
MessageBackupTier.PAID,
System.currentTimeMillis(),
true
)
}
}
@Composable
private fun RestoreFromBackupContent(
features: ImmutableList<MessageBackupsTypeFeature>,
onRestoreBackupClick: () -> Unit,
onCancelClick: () -> Unit,
onMoreOptionsClick: () -> Unit,
tier: MessageBackupTier?,
lastBackupTime: Long,
cancelable: Boolean
) {
Column(
modifier = Modifier
.padding(horizontal = dimensionResource(id = CoreUiR.dimen.gutter))
.padding(top = 40.dp, bottom = 24.dp)
) {
Text(
text = stringResource(id = R.string.RemoteRestoreActivity__restore_from_backup),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 12.dp)
)
val yourLastBackupText = buildAnnotatedString {
append(
stringResource(
id = R.string.RemoteRestoreActivity__backup_created_at,
DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), lastBackupTime),
DateUtils.getOnlyTimeString(LocalContext.current, lastBackupTime)
)
)
append(" ")
if (tier != MessageBackupTier.PAID) {
withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
append(stringResource(id = R.string.RemoteRestoreActivity__only_media_sent_or_received))
}
}
}
Text(
text = yourLastBackupText,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = 28.dp)
)
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = SignalTheme.colors.colorSurface2, shape = RoundedCornerShape(18.dp))
.padding(horizontal = 20.dp)
.padding(top = 20.dp, bottom = 18.dp)
) {
Text(
text = stringResource(id = R.string.RemoteRestoreActivity__your_backup_includes),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 6.dp)
)
features.forEach {
MessageBackupsTypeFeatureRow(
messageBackupsTypeFeature = it,
iconTint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 16.dp, top = 6.dp)
)
}
}
Spacer(modifier = Modifier.weight(1f))
Buttons.LargeTonal(
onClick = onRestoreBackupClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.RemoteRestoreActivity__restore_backup)
)
}
if (cancelable) {
TextButton(
onClick = onCancelClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = android.R.string.cancel)
)
}
} else {
TextButton(
onClick = onMoreOptionsClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.TransferOrRestoreFragment__more_options)
)
}
}
}
}
private fun restoreFromServer() {
viewModel.restore()
}
private fun continueRegistration() {
if (Recipient.self().profileName.isEmpty || !AvatarHelper.hasAvatar(this, Recipient.self().id)) {
val main = MainActivity.clearTop(this)
val profile = CreateProfileActivity.getIntentForUserProfile(this)
profile.putExtra("next_intent", main)
startActivity(profile)
} else {
RegistrationUtil.maybeMarkRegistrationComplete()
AppDependencies.jobManager.add(ProfileUploadJob())
startActivity(MainActivity.clearTop(this))
}
finish()
}
@Composable
private fun StateLabel(text: String) {
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center
)
}
}

View File

@@ -83,7 +83,7 @@ class WelcomeFragment : LoggingFragment(R.layout.fragment_registration_welcome)
} else {
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PERMISSIONS_GRANTED)
val restoreIntent = RestoreActivity.getIntentForTransferOrRestore(requireActivity())
val restoreIntent = RestoreActivity.getRestoreIntent(requireActivity())
launchRestoreActivity.launch(restoreIntent)
}
}

View File

@@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobs.StorageSyncJob;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.RemoteConfig;
public final class RegistrationUtil {
@@ -29,7 +30,8 @@ public final class RegistrationUtil {
if (!SignalStore.registration().isRegistrationComplete() &&
SignalStore.account().isRegistered() &&
!Recipient.self().getProfileName().isEmpty() &&
(SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut()))
(SignalStore.svr().hasOptedInWithAccess() || SignalStore.svr().hasOptedOut()) &&
(RemoteConfig.restoreAfterRegistration() && (SignalStore.registration().hasSkippedTransferOrRestore() || SignalStore.registration().hasCompletedRestore())))
{
Log.i(TAG, "Marking registration completed.", new Throwable());
SignalStore.registration().setRegistrationComplete();