mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-10-05 15:23:29 +01:00
Streamline export account data to not save to disk.
This commit is contained in:
1 parent
5e94c350ed
commit
ad9337021c
9 files changed
+80
-221
No files matched your search
@@ -492,9 +492,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
private void initializeCleanup() {
|
||||
int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments();
|
||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||
if (SignalStore.account().clearOldAccountDataReport()) {
|
||||
Log.i(TAG, "Deleted " + deleted + " expired account data report.");
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeGlideCodecs() {
|
||||
|
||||
+22
-66
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.account.export
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement.Center
|
||||
@@ -10,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
@@ -32,7 +33,7 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dialogs
|
||||
import org.signal.core.ui.Rows
|
||||
@@ -42,6 +43,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
|
||||
class ExportAccountDataFragment : ComposeFragment() {
|
||||
@@ -52,27 +54,29 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: ExportAccountDataViewModel by viewModels()
|
||||
|
||||
private fun deleteReport() {
|
||||
viewModel.deleteReport()
|
||||
Snackbar.make(requireView(), R.string.ExportAccountDataFragment__delete_report_snackbar, Snackbar.LENGTH_SHORT).show()
|
||||
private val disposables = LifecycleDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
}
|
||||
|
||||
private fun exportReport() {
|
||||
val report = viewModel.onGenerateReport()
|
||||
ShareCompat.IntentBuilder(requireContext())
|
||||
.setStream(report.uri)
|
||||
.setType(report.mimeType)
|
||||
.startChooser()
|
||||
disposables += viewModel.onGenerateReport()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { report ->
|
||||
ShareCompat.IntentBuilder(requireContext())
|
||||
.setStream(report.uri)
|
||||
.setType(report.mimeType)
|
||||
.startChooser()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissExportDialog() {
|
||||
viewModel.dismissExportConfirmationDialog()
|
||||
}
|
||||
|
||||
private fun dismissDeleteDialog() {
|
||||
viewModel.dismissDeleteConfirmationDialog()
|
||||
}
|
||||
|
||||
private fun dismissDownloadErrorDialog() {
|
||||
viewModel.dismissDownloadErrorDialog()
|
||||
}
|
||||
@@ -128,11 +132,7 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
item {
|
||||
if (state.reportDownloaded) {
|
||||
ExportReportOptions(exportAsJson = state.exportAsJson)
|
||||
} else {
|
||||
DownloadReportOptions()
|
||||
}
|
||||
ExportReportOptions(exportAsJson = state.exportAsJson)
|
||||
}
|
||||
}
|
||||
if (state.downloadInProgress) {
|
||||
@@ -141,8 +141,6 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
DownloadFailedDialog()
|
||||
} else if (state.showExportDialog) {
|
||||
ExportReportConfirmationDialog()
|
||||
} else if (state.showDeleteDialog) {
|
||||
DeleteReportConfirmationDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,25 +173,13 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
@Composable
|
||||
private fun DownloadFailedDialog() {
|
||||
Dialogs.SimpleMessageDialog(
|
||||
message = stringResource(id = R.string.ExportAccountDataFragment__report_download_failed),
|
||||
message = stringResource(id = R.string.ExportAccountDataFragment__check_network),
|
||||
dismiss = stringResource(id = R.string.ExportAccountDataFragment__ok_action),
|
||||
title = stringResource(id = R.string.ExportAccountDataFragment__report_generation_failed),
|
||||
onDismiss = this::dismissDownloadErrorDialog
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeleteReportConfirmationDialog() {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.ExportAccountDataFragment__delete_report_confirmation),
|
||||
body = stringResource(R.string.ExportAccountDataFragment__delete_report_confirmation_message),
|
||||
confirm = stringResource(R.string.ExportAccountDataFragment__delete_report_action),
|
||||
dismiss = stringResource(R.string.ExportAccountDataFragment__cancel_action),
|
||||
onConfirm = this::deleteReport,
|
||||
onDismiss = this::dismissDeleteDialog,
|
||||
confirmColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExportReportConfirmationDialog() {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
@@ -206,22 +192,6 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DownloadReportOptions() {
|
||||
Buttons.LargeTonal(
|
||||
onClick = viewModel::onDownloadReport,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 52.dp, start = 32.dp, end = 32.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.ExportAccountDataFragment__download_report),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExportReportOptions(exportAsJson: Boolean) {
|
||||
Rows.RadioRow(
|
||||
@@ -255,22 +225,8 @@ class ExportAccountDataFragment : ComposeFragment() {
|
||||
)
|
||||
}
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = viewModel::showDeleteConfirmationDialog,
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 14.dp, start = 32.dp, end = 32.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.ExportAccountDataFragment__delete_report),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.ExportAccountDataFragment__report_deletion_disclaimer),
|
||||
text = stringResource(id = R.string.ExportAccountDataFragment__report_not_stored_disclaimer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 28.dp, bottom = 20.dp)
|
||||
|
||||
+7
-9
@@ -3,10 +3,9 @@ package org.thoughtcrime.securesms.components.settings.app.account.export
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
@@ -16,18 +15,17 @@ class ExportAccountDataRepository(
|
||||
private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager()
|
||||
) {
|
||||
|
||||
fun downloadAccountDataReport(): Completable {
|
||||
return Completable.create {
|
||||
fun downloadAccountDataReport(exportAsJson: Boolean): Single<ExportedReport> {
|
||||
return Single.create {
|
||||
try {
|
||||
SignalStore.account().setAccountDataReport(accountManager.accountDataReport, System.currentTimeMillis())
|
||||
it.onComplete()
|
||||
it.onSuccess(generateAccountDataReport(accountManager.accountDataReport, exportAsJson))
|
||||
} catch (e: IOException) {
|
||||
it.onError(e)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun generateAccountDataReport(exportAsJson: Boolean): ExportedReport {
|
||||
private fun generateAccountDataReport(report: String, exportAsJson: Boolean): ExportedReport {
|
||||
val mimeType: String
|
||||
val fileName: String
|
||||
if (exportAsJson) {
|
||||
@@ -38,7 +36,7 @@ class ExportAccountDataRepository(
|
||||
fileName = "account-data.txt"
|
||||
}
|
||||
|
||||
val tree: JsonNode = JsonUtils.getMapper().readTree(SignalStore.account().accountDataReport)
|
||||
val tree: JsonNode = JsonUtils.getMapper().readTree(report)
|
||||
val dataStr = if (exportAsJson) {
|
||||
(tree as ObjectNode).remove("text")
|
||||
tree.toString()
|
||||
@@ -50,7 +48,7 @@ class ExportAccountDataRepository(
|
||||
.forData(dataStr.encodeToByteArray())
|
||||
.withMimeType(mimeType)
|
||||
.withFileName(fileName)
|
||||
.createForSingleSessionInMemory()
|
||||
.createForSingleUseInMemory()
|
||||
|
||||
return ExportedReport(mimeType = mimeType, uri = uri)
|
||||
}
|
||||
|
||||
-2
@@ -1,10 +1,8 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.account.export
|
||||
|
||||
data class ExportAccountDataState(
|
||||
val reportDownloaded: Boolean,
|
||||
val downloadInProgress: Boolean,
|
||||
val exportAsJson: Boolean,
|
||||
val showDownloadFailedDialog: Boolean = false,
|
||||
val showDeleteDialog: Boolean = false,
|
||||
val showExportDialog: Boolean = false
|
||||
)
|
||||
+11
-24
@@ -4,10 +4,11 @@ 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.Maybe
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.subjects.MaybeSubject
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class ExportAccountDataViewModel(
|
||||
private val repository: ExportAccountDataRepository = ExportAccountDataRepository()
|
||||
@@ -20,26 +21,25 @@ class ExportAccountDataViewModel(
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val _state = mutableStateOf(
|
||||
ExportAccountDataState(reportDownloaded = false, downloadInProgress = false, exportAsJson = false)
|
||||
ExportAccountDataState(downloadInProgress = false, exportAsJson = false)
|
||||
)
|
||||
|
||||
val state: State<ExportAccountDataState> = _state
|
||||
|
||||
init {
|
||||
_state.value = _state.value.copy(reportDownloaded = SignalStore.account().hasAccountDataReport())
|
||||
}
|
||||
|
||||
fun onGenerateReport(): ExportAccountDataRepository.ExportedReport = repository.generateAccountDataReport(state.value.exportAsJson)
|
||||
fun onDownloadReport() {
|
||||
fun onGenerateReport(): Maybe<ExportAccountDataRepository.ExportedReport> {
|
||||
_state.value = _state.value.copy(downloadInProgress = true)
|
||||
disposables += repository.downloadAccountDataReport()
|
||||
val maybe = MaybeSubject.create<ExportAccountDataRepository.ExportedReport>()
|
||||
disposables += repository.downloadAccountDataReport(state.value.exportAsJson)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
_state.value = _state.value.copy(downloadInProgress = false, reportDownloaded = true)
|
||||
.subscribe({ report ->
|
||||
_state.value = _state.value.copy(downloadInProgress = false)
|
||||
maybe.onSuccess(report)
|
||||
}, { throwable ->
|
||||
Log.e(TAG, throwable)
|
||||
_state.value = _state.value.copy(downloadInProgress = false, showDownloadFailedDialog = true)
|
||||
maybe.onComplete()
|
||||
})
|
||||
return maybe
|
||||
}
|
||||
|
||||
fun setExportAsJson() {
|
||||
@@ -50,14 +50,6 @@ class ExportAccountDataViewModel(
|
||||
_state.value = _state.value.copy(exportAsJson = false)
|
||||
}
|
||||
|
||||
fun showDeleteConfirmationDialog() {
|
||||
_state.value = _state.value.copy(showDeleteDialog = true)
|
||||
}
|
||||
|
||||
fun dismissDeleteConfirmationDialog() {
|
||||
_state.value = _state.value.copy(showDeleteDialog = false)
|
||||
}
|
||||
|
||||
fun dismissDownloadErrorDialog() {
|
||||
_state.value = _state.value.copy(showDownloadFailedDialog = false)
|
||||
}
|
||||
@@ -70,11 +62,6 @@ class ExportAccountDataViewModel(
|
||||
_state.value = _state.value.copy(showExportDialog = false)
|
||||
}
|
||||
|
||||
fun deleteReport() {
|
||||
SignalStore.account().deleteAccountDataReport()
|
||||
_state.value = _state.value.copy(reportDownloaded = false)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.security.SecureRandom
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
internal class AccountValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
|
||||
@@ -58,12 +57,6 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
private const val KEY_PNI_SIGNED_PREKEY_FAILURE_COUNT = "account.pni_signed_prekey_failure_count"
|
||||
private const val KEY_PNI_NEXT_ONE_TIME_PREKEY_ID = "account.pni_next_one_time_prekey_id"
|
||||
|
||||
@VisibleForTesting
|
||||
const val KEY_ACCOUNT_DATA_REPORT = "account.data_report"
|
||||
|
||||
@VisibleForTesting
|
||||
const val KEY_ACCOUNT_DATA_REPORT_DOWNLOAD_TIME = "account.data_report_download_time"
|
||||
|
||||
@VisibleForTesting
|
||||
const val KEY_E164 = "account.e164"
|
||||
|
||||
@@ -324,34 +317,6 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
}
|
||||
}
|
||||
|
||||
val accountDataReport: String?
|
||||
get() = getString(KEY_ACCOUNT_DATA_REPORT, null)
|
||||
|
||||
fun setAccountDataReport(report: String, downloadTime: Long) {
|
||||
store.beginWrite()
|
||||
.putString(KEY_ACCOUNT_DATA_REPORT, report)
|
||||
.putLong(KEY_ACCOUNT_DATA_REPORT_DOWNLOAD_TIME, downloadTime)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun hasAccountDataReport(): Boolean = store.containsKey(KEY_ACCOUNT_DATA_REPORT)
|
||||
|
||||
fun clearOldAccountDataReport(): Boolean {
|
||||
return if (hasAccountDataReport() && (getLong(KEY_ACCOUNT_DATA_REPORT_DOWNLOAD_TIME, 0) + 30.days.inWholeMilliseconds) < System.currentTimeMillis()) {
|
||||
deleteAccountDataReport()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAccountDataReport() {
|
||||
store.beginWrite()
|
||||
.remove(KEY_ACCOUNT_DATA_REPORT)
|
||||
.remove(KEY_ACCOUNT_DATA_REPORT_DOWNLOAD_TIME)
|
||||
.apply()
|
||||
}
|
||||
|
||||
val deviceName: String?
|
||||
get() = getString(KEY_DEVICE_NAME, null)
|
||||
|
||||
|
||||
@@ -3980,21 +3980,17 @@
|
||||
<string name="AccountSettingsFragment__require_your_signal_pin">Require your Signal PIN to register your phone number with Signal again</string>
|
||||
<string name="AccountSettingsFragment__change_phone_number">Change phone number</string>
|
||||
<!-- Account setting that allows user to request and export their signal account data -->
|
||||
<string name="AccountSettingsFragment__request_account_data">Request account data</string>
|
||||
<string name="AccountSettingsFragment__request_account_data">Your account data</string>
|
||||
|
||||
<!-- ExportAccountDataFragment -->
|
||||
<!-- Part of requesting account data flow, this is the section title for requesting that account data -->
|
||||
<string name="ExportAccountDataFragment__your_account_data">Your account data</string>
|
||||
<!-- Explanation of account data the user can request. %1$s is replaced with Learn more with a link -->
|
||||
<string name="ExportAccountDataFragment__export_explanation">Download and export a report of your Signal account data. This report does not include any messages or media. %1$s</string>
|
||||
<string name="ExportAccountDataFragment__export_explanation">Export a report of your Signal account data. This report does not include any messages or media. %1$s</string>
|
||||
<!-- Learn more link to more information about requesting account data -->
|
||||
<string name="ExportAccountDataFragment__learn_more">Learn more</string>
|
||||
<!-- Button action to download the report data -->
|
||||
<string name="ExportAccountDataFragment__download_report">Download report</string>
|
||||
<!-- Button action to export the report data to another app (e.g. email) -->
|
||||
<string name="ExportAccountDataFragment__export_report">Export report</string>
|
||||
<!-- Button action to delete the requested account data -->
|
||||
<string name="ExportAccountDataFragment__delete_report">Delete report</string>
|
||||
|
||||
<!-- Radio option to export the data as a text file .txt -->
|
||||
<string name="ExportAccountDataFragment__export_as_txt">Export as TXT</string>
|
||||
@@ -4010,8 +4006,10 @@
|
||||
|
||||
<!-- Acknowledgement for download failure -->
|
||||
<string name="ExportAccountDataFragment__ok_action">OK</string>
|
||||
<!-- Message shown when report download fails -->
|
||||
<string name="ExportAccountDataFragment__report_download_failed">Your report could not be downloaded due to a network error. Check your connection and try again.</string>
|
||||
<!-- Title of dialog shown when report fails to generate -->
|
||||
<string name="ExportAccountDataFragment__report_generation_failed">Couldn\'t generate report</string>
|
||||
<!-- Message of dialog shown when report fails to generate asking user to check network connection -->
|
||||
<string name="ExportAccountDataFragment__check_network">Check your connection and try again.</string>
|
||||
|
||||
<!-- Title for export confirmation dialog -->
|
||||
<string name="ExportAccountDataFragment__export_report_confirmation">Export data?</string>
|
||||
@@ -4020,19 +4018,10 @@
|
||||
<!-- Action to export in for export confirmation dialog -->
|
||||
<string name="ExportAccountDataFragment__export_report_action">Export</string>
|
||||
|
||||
<!-- Action to delete report in confirmation dialog -->
|
||||
<string name="ExportAccountDataFragment__delete_report_action">Delete</string>
|
||||
<!-- Title for delete report confirmation dialog -->
|
||||
<string name="ExportAccountDataFragment__delete_report_confirmation">Delete report?</string>
|
||||
<!-- Message for delete report confirmation dialog -->
|
||||
<string name="ExportAccountDataFragment__delete_report_confirmation_message">This will not delete any data from your account.</string>
|
||||
|
||||
<!-- Shown as a snackbar letting the user know the report was deleted -->
|
||||
<string name="ExportAccountDataFragment__delete_report_snackbar">Report deleted</string>
|
||||
<!-- Shown in a dialog with a spinner while the report is downloading -->
|
||||
<string name="ExportAccountDataFragment__download_progress">Downloading report…</string>
|
||||
<!-- Explanation that the downloaded report will be available for 30 days before it is deleted -->
|
||||
<string name="ExportAccountDataFragment__report_deletion_disclaimer">Your report will be available for export for 30 days and will then be automatically deleted.</string>
|
||||
<string name="ExportAccountDataFragment__download_progress">Generating report…</string>
|
||||
<!-- Explanation that the report is only generated on export and is not saved on the device -->
|
||||
<string name="ExportAccountDataFragment__report_not_stored_disclaimer">Your report is generated only at the time of export and is not stored by Signal on your device.</string>
|
||||
|
||||
<!-- ChangeNumberFragment -->
|
||||
<string name="ChangeNumberFragment__use_this_to_change_your_current_phone_number_to_a_new_phone_number">Use this to change your current phone number to a new phone number. You can’t undo this change.\n\nBefore continuing, make sure your new number can receive SMS or calls.</string>
|
||||
|
||||
+29
-62
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonParseException
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.reactivex.rxjava3.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.TestScheduler
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -19,13 +19,9 @@ import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
|
||||
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@@ -75,86 +71,57 @@ class ExportAccountDataTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Successful download flow with progress states and delete flow`() {
|
||||
val dataSet = KeyValueDataSet()
|
||||
val scheduler = TestScheduler()
|
||||
val mockRepository: ExportAccountDataRepository = mockk {
|
||||
every { downloadAccountDataReport() } returns Completable.create {
|
||||
dataSet.putString(AccountValues.KEY_ACCOUNT_DATA_REPORT, mockJson)
|
||||
dataSet.putLong(AccountValues.KEY_ACCOUNT_DATA_REPORT_DOWNLOAD_TIME, 123456L)
|
||||
it.onComplete()
|
||||
}.subscribeOn(scheduler)
|
||||
}
|
||||
|
||||
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
|
||||
RxAndroidPlugins.setMainThreadSchedulerHandler {
|
||||
scheduler
|
||||
}
|
||||
|
||||
val viewModel = ExportAccountDataViewModel(mockRepository)
|
||||
assertFalse(viewModel.state.value.reportDownloaded)
|
||||
viewModel.onDownloadReport()
|
||||
assertTrue(viewModel.state.value.downloadInProgress)
|
||||
scheduler.triggerActions()
|
||||
assertTrue(viewModel.state.value.reportDownloaded)
|
||||
assertFalse(viewModel.state.value.downloadInProgress)
|
||||
|
||||
assertEquals(SignalStore.account().accountDataReport, mockJson)
|
||||
viewModel.deleteReport()
|
||||
assertEquals(SignalStore.account().accountDataReport, null)
|
||||
assertFalse(viewModel.state.value.reportDownloaded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Export json without text field`() {
|
||||
val dataSet = KeyValueDataSet()
|
||||
dataSet.putString(AccountValues.KEY_ACCOUNT_DATA_REPORT, mockJson)
|
||||
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
|
||||
|
||||
val mockRepository: ExportAccountDataRepository = mockk {
|
||||
every { generateAccountDataReport(any()) } answers { callOriginal() }
|
||||
val scheduler = TestScheduler()
|
||||
val accountManager: SignalServiceAccountManager = mockk {
|
||||
every { accountDataReport } returns mockJson
|
||||
}
|
||||
val mockRepository = ExportAccountDataRepository(accountManager)
|
||||
val viewModel = ExportAccountDataViewModel(mockRepository)
|
||||
|
||||
viewModel.setExportAsTxt()
|
||||
val txtReport = viewModel.onGenerateReport()
|
||||
assertEquals(txtReport.mimeType, "text/plain")
|
||||
assertThrows(JsonParseException::class.java) {
|
||||
JsonUtils.getMapper().readTree(BlobProvider.getInstance().getMemoryBlob(txtReport.uri) as ByteArray)
|
||||
}
|
||||
|
||||
viewModel.onGenerateReport()
|
||||
.observeOn(scheduler)
|
||||
.subscribe { txtReport ->
|
||||
assertEquals(txtReport.mimeType, "text/plain")
|
||||
assertThrows(JsonParseException::class.java) {
|
||||
JsonUtils.getMapper().readTree(BlobProvider.getInstance().getMemoryBlob(txtReport.uri) as ByteArray)
|
||||
}
|
||||
}
|
||||
scheduler.triggerActions()
|
||||
viewModel.setExportAsJson()
|
||||
val jsonReport = viewModel.onGenerateReport()
|
||||
assertEquals(jsonReport.mimeType, "application/json")
|
||||
val json = JsonUtils.getMapper().readTree(BlobProvider.getInstance().getMemoryBlob(jsonReport.uri) as ByteArray)
|
||||
assertFalse(json.has("text"))
|
||||
assertTrue(json.has("data"))
|
||||
assertTrue(json.has("reportId"))
|
||||
assertTrue(json.has("reportTimestamp"))
|
||||
viewModel.onGenerateReport()
|
||||
.observeOn(scheduler)
|
||||
.subscribe { jsonReport ->
|
||||
assertEquals(jsonReport.mimeType, "application/json")
|
||||
val json = JsonUtils.getMapper().readTree(BlobProvider.getInstance().getMemoryBlob(jsonReport.uri) as ByteArray)
|
||||
assertFalse(json.has("text"))
|
||||
assertTrue(json.has("data"))
|
||||
assertTrue(json.has("reportId"))
|
||||
assertTrue(json.has("reportTimestamp"))
|
||||
}
|
||||
scheduler.triggerActions()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Failed download error flow`() {
|
||||
val dataSet = KeyValueDataSet()
|
||||
val scheduler = TestScheduler()
|
||||
val mockRepository: ExportAccountDataRepository = mockk {
|
||||
every { downloadAccountDataReport() } returns Completable.create {
|
||||
every { downloadAccountDataReport(any()) } returns Single.create<ExportAccountDataRepository.ExportedReport> {
|
||||
it.onError(IOException())
|
||||
}.subscribeOn(scheduler)
|
||||
}
|
||||
|
||||
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
|
||||
RxAndroidPlugins.setMainThreadSchedulerHandler {
|
||||
scheduler
|
||||
}
|
||||
|
||||
val viewModel = ExportAccountDataViewModel(mockRepository)
|
||||
assertEquals(viewModel.state.value.reportDownloaded, false)
|
||||
viewModel.onDownloadReport()
|
||||
assertEquals(viewModel.state.value.downloadInProgress, false)
|
||||
viewModel.onGenerateReport()
|
||||
assertEquals(viewModel.state.value.downloadInProgress, true)
|
||||
scheduler.triggerActions()
|
||||
assertEquals(viewModel.state.value.reportDownloaded, false)
|
||||
assertEquals(viewModel.state.value.downloadInProgress, false)
|
||||
|
||||
assertEquals(viewModel.state.value.showDownloadFailedDialog, true)
|
||||
|
||||
@@ -18,11 +18,13 @@ object Dialogs {
|
||||
dismiss: String,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
dismissColor: Color = Color.Unspecified,
|
||||
properties: DialogProperties = DialogProperties()
|
||||
) {
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = if (title == null) null else { { Text(text = title) } },
|
||||
text = { Text(text = message) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
|
||||
Reference in new issue
Block a user