From 742c3489985e973d4f4b8a560aca284ad247cda2 Mon Sep 17 00:00:00 2001 From: Clark Date: Fri, 8 Mar 2024 16:27:39 -0500 Subject: [PATCH] Add test restore flow to staging reg. --- app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 4 + .../ui/MessageBackupsTestRestoreActivity.kt | 148 ++++++++++++++++++ .../ui/MessageBackupsTestRestoreViewModel.kt | 59 +++++++ .../pin/PinRestoreEntryFragment.java | 6 +- 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/MessageBackupsTestRestoreActivity.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/MessageBackupsTestRestoreViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4848fc2ac1..5f9b01f8f5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -216,6 +216,7 @@ android { buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"") buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"") buildConfigField("boolean", "TRACING_ENABLED", "false") + buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "false") ndk { abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") @@ -390,6 +391,7 @@ android { buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"") buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"") + buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true") } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 850902b4a3..e39e89c4a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -961,6 +961,10 @@ android:windowSoftInputMode="stateVisible|adjustResize" android:exported="false"/> + + + + private fun onPlaintextClicked() { + viewModel.onPlaintextToggled() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + importFileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + result.data?.data?.let { uri -> + contentResolver.getLength(uri)?.let { length -> + viewModel.import(length) { contentResolver.openInputStream(uri)!! } + } + } ?: Toast.makeText(this, "No URI selected", Toast.LENGTH_SHORT).show() + } + } + + setContent { + val state by viewModel.state + Surface { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + StateLabel(text = "Plaintext?") + Spacer(modifier = Modifier.width(8.dp)) + Switch( + checked = state.plaintext, + onCheckedChange = { onPlaintextClicked() } + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Buttons.LargePrimary( + onClick = { + val intent = Intent().apply { + action = Intent.ACTION_GET_CONTENT + type = "application/octet-stream" + addCategory(Intent.CATEGORY_OPENABLE) + } + + importFileLauncher.launch(intent) + }, + enabled = !state.importState.inProgress + ) { + Text("Import from file") + } + + Spacer(modifier = Modifier.height(8.dp)) + + Dividers.Default() + + Buttons.LargeTonal( + onClick = { continueRegistration() }, + enabled = !state.importState.inProgress + ) { + Text("Continue Reg Flow") + } + } + } + } + } + + 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() + ApplicationDependencies.getJobManager().add(ProfileUploadJob()) + startActivity(MainActivity.clearTop(this)) + } + finish() + } + + @Composable + private fun StateLabel(text: String) { + Text( + text = text, + style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.Center + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/MessageBackupsTestRestoreViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/MessageBackupsTestRestoreViewModel.kt new file mode 100644 index 0000000000..5213febed5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/MessageBackupsTestRestoreViewModel.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.ui + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import io.reactivex.rxjava3.kotlin.subscribeBy +import io.reactivex.rxjava3.schedulers.Schedulers +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.recipients.Recipient +import java.io.InputStream + +class MessageBackupsTestRestoreViewModel : ViewModel() { + val disposables = CompositeDisposable() + + private val _state: MutableState = mutableStateOf(ScreenState(importState = ImportState.NONE, plaintext = false)) + val state: State = _state + + fun import(length: Long, inputStreamFactory: () -> InputStream) { + _state.value = _state.value.copy(importState = ImportState.IN_PROGRESS) + + val self = Recipient.self() + val selfData = BackupRepository.SelfData(self.aci.get(), self.pni.get(), self.e164.get(), ProfileKey(self.profileKey)) + + disposables += Single.fromCallable { BackupRepository.import(length, inputStreamFactory, selfData, plaintext = _state.value.plaintext) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy { + _state.value = _state.value.copy(importState = ImportState.NONE) + } + } + + fun onPlaintextToggled() { + _state.value = _state.value.copy(plaintext = !_state.value.plaintext) + } + + override fun onCleared() { + disposables.clear() + } + + data class ScreenState( + val importState: ImportState, + val plaintext: Boolean + ) + + enum class ImportState(val inProgress: Boolean = false) { + NONE, IN_PROGRESS(true) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java index d711cbe1f7..5486fced5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java @@ -23,9 +23,11 @@ import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.backup.v2.ui.MessageBackupsTestRestoreActivity; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -236,7 +238,9 @@ public class PinRestoreEntryFragment extends LoggingFragment { Activity activity = requireActivity(); - if (Recipient.self().getProfileName().isEmpty() || !AvatarHelper.hasAvatar(activity, Recipient.self().getId())) { + if (BuildConfig.MESSAGE_BACKUP_RESTORE_ENABLED) { + startActivity(MessageBackupsTestRestoreActivity.Companion.getIntent(activity)); + } else if (Recipient.self().getProfileName().isEmpty() || !AvatarHelper.hasAvatar(activity, Recipient.self().getId())) { final Intent main = MainActivity.clearTop(activity); final Intent profile = CreateProfileActivity.getIntentForUserProfile(activity);