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);