[SS-54] Add dialog to allow local deletion if network deletion fails (#1526)

* WIP

* Push before attempting some HTTPRequestFailedException rate limiting

* Functionality now works

* Merging dev resulted in some subproject commit change so pushing that

* Fixes #1525

* Addressed Andy PR feedback

* Addressed further PR feedback from Andy

---------

Co-authored-by: alansley <aclansley@gmail.com>
This commit is contained in:
AL-Session 2024-07-02 16:42:49 +10:00 committed by GitHub
parent 0da949c8e6
commit a30f00104e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 89 additions and 56 deletions

View File

@ -501,7 +501,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
});
}
public void clearAllData(boolean isMigratingToV2KeyPair) {
// Method to clear the local data - returns true on success otherwise false
/**
* Clear all local profile data and message history then restart the app after a brief delay.
* @param isMigratingToV2KeyPair whether we're upgrading to a more recent V2 key pair or not.
* @return true on success, false otherwise.
*/
public boolean clearAllData(boolean isMigratingToV2KeyPair) {
if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) {
firebaseInstanceIdJob.cancel(null);
}
@ -515,9 +522,11 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
if (!deleteDatabase(SQLCipherOpenHelper.DATABASE_NAME)) {
Log.d("Loki", "Failed to delete database.");
return false;
}
configFactory.keyPairChanged();
Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
return true;
}
public void restartApplication() {

View File

@ -4,12 +4,13 @@ import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isGone
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -24,17 +25,26 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
class ClearAllDataDialog : DialogFragment() {
private val TAG = "ClearAllDataDialog"
private lateinit var binding: DialogClearAllDataBinding
enum class Steps {
private enum class Steps {
INFO_PROMPT,
NETWORK_PROMPT,
DELETING
DELETING,
RETRY_LOCAL_DELETE_ONLY_PROMPT
}
var clearJob: Job? = null
// Rather than passing a bool around we'll use an enum to clarify our intent
private enum class DeletionScope {
DeleteLocalDataOnly,
DeleteBothLocalAndNetworkData
}
var step = Steps.INFO_PROMPT
private var clearJob: Job? = null
private var step = Steps.INFO_PROMPT
set(value) {
field = value
updateUI()
@ -46,8 +56,8 @@ class ClearAllDataDialog : DialogFragment() {
private fun createView(): View {
binding = DialogClearAllDataBinding.inflate(LayoutInflater.from(requireContext()))
val device = radioOption("deviceOnly", R.string.dialog_clear_all_data_clear_device_only)
val network = radioOption("deviceAndNetwork", R.string.dialog_clear_all_data_clear_device_and_network)
val device = radioOption("deviceOnly", R.string.clearDeviceOnly)
val network = radioOption("deviceAndNetwork", R.string.clearDeviceAndNetwork)
var selectedOption: RadioOption<String> = device
val optionAdapter = RadioOptionAdapter { selectedOption = it }
binding.recyclerView.apply {
@ -57,18 +67,21 @@ class ClearAllDataDialog : DialogFragment() {
setHasFixedSize(true)
}
optionAdapter.submitList(listOf(device, network))
binding.cancelButton.setOnClickListener {
dismiss()
}
binding.clearAllDataButton.setOnClickListener {
when (step) {
Steps.INFO_PROMPT -> if (selectedOption == network) {
step = Steps.NETWORK_PROMPT
} else {
clearAllData(false)
clearAllData(DeletionScope.DeleteLocalDataOnly)
}
Steps.NETWORK_PROMPT -> clearAllData(true)
Steps.NETWORK_PROMPT -> clearAllData(DeletionScope.DeleteBothLocalAndNetworkData)
Steps.DELETING -> { /* do nothing intentionally */ }
Steps.RETRY_LOCAL_DELETE_ONLY_PROMPT -> clearAllData(DeletionScope.DeleteLocalDataOnly)
}
}
return binding.root
@ -86,8 +99,13 @@ class ClearAllDataDialog : DialogFragment() {
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_clear_device_and_network_confirmation)
}
Steps.DELETING -> { /* do nothing intentionally */ }
Steps.RETRY_LOCAL_DELETE_ONLY_PROMPT -> {
binding.dialogDescriptionText.setText(R.string.clearDataErrorDescriptionGeneric)
binding.clearAllDataButton.text = getString(R.string.clearDevice)
}
binding.recyclerView.isGone = step == Steps.NETWORK_PROMPT
}
binding.recyclerView.isVisible = step == Steps.INFO_PROMPT
binding.cancelButton.isVisible = !isLoading
binding.clearAllDataButton.isVisible = !isLoading
binding.progressBar.isVisible = isLoading
@ -97,45 +115,55 @@ class ClearAllDataDialog : DialogFragment() {
}
}
private fun clearAllData(deleteNetworkMessages: Boolean) {
clearJob = lifecycleScope.launch(Dispatchers.IO) {
val previousStep = step
withContext(Dispatchers.Main) {
step = Steps.DELETING
}
if (!deleteNetworkMessages) {
private suspend fun performDeleteLocalDataOnlyStep() {
try {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(requireContext()).get()
} catch (e: Exception) {
Log.e("Loki", "Failed to force sync", e)
Log.e(TAG, "Failed to force sync when deleting data", e)
withContext(Main) {
Toast.makeText(ApplicationContext.getInstance(requireContext()), R.string.errorUnknown, Toast.LENGTH_LONG).show()
}
ApplicationContext.getInstance(context).clearAllData(false)
withContext(Dispatchers.Main) {
return
}
ApplicationContext.getInstance(context).clearAllData(false).let { success ->
withContext(Main) {
if (success) {
dismiss()
}
} else {
// finish
val result = try {
Toast.makeText(ApplicationContext.getInstance(requireContext()), R.string.errorUnknown, Toast.LENGTH_LONG).show()
}
}
}
}
private fun clearAllData(deletionScope: DeletionScope) {
step = Steps.DELETING
clearJob = lifecycleScope.launch(Dispatchers.IO) {
when (deletionScope) {
DeletionScope.DeleteLocalDataOnly -> {
performDeleteLocalDataOnlyStep()
}
DeletionScope.DeleteBothLocalAndNetworkData -> {
val deletionResultMap: Map<String, Boolean>? = try {
val openGroups = DatabaseComponent.get(requireContext()).lokiThreadDatabase().getAllOpenGroups()
openGroups.map { it.value.server }.toSet().forEach { server ->
OpenGroupApi.deleteAllInboxMessages(server).get()
}
SnodeAPI.deleteAllMessages().get()
} catch (e: Exception) {
Log.e(TAG, "Failed to delete network messages - offering user option to delete local data only.", e)
null
}
if (result == null || result.values.any { !it } || result.isEmpty()) {
// didn't succeed (at least one)
withContext(Dispatchers.Main) {
step = previousStep
// If one or more deletions failed then inform the user and allow them to clear the device only if they wish..
if (deletionResultMap == null || deletionResultMap.values.any { !it } || deletionResultMap.isEmpty()) {
withContext(Main) { step = Steps.RETRY_LOCAL_DELETE_ONLY_PROMPT }
}
} else if (result.values.all { it }) {
// don't force sync because all the messages are deleted?
else if (deletionResultMap.values.all { it }) {
// ..otherwise if the network data deletion was successful proceed to delete the local data as well.
ApplicationContext.getInstance(context).clearAllData(false)
withContext(Dispatchers.Main) {
dismiss()
withContext(Main) { dismiss() }
}
}
}

View File

@ -657,8 +657,6 @@
<string name="dialog_clear_all_data_explanation">این گزینه به طور دائم پیام‌ها، جلسات و مخاطبین شما را حذف می‌کند.</string>
<string name="dialog_clear_all_data_network_explanation">آیا فقط می‌خواهید این دستگاه را پاک کنید یا می‌خواهید کل اکانت را پاک کنید؟</string>
<string name="dialog_clear_all_data_message">این کار پیام‌ها و مخاطبین شما را برای همیشه حذف می‌کند. آیا می‌خواهید فقط این دستگاه را پاک کنید یا داتا خود را از شبکه نیز حذف کنید?</string>
<string name="dialog_clear_all_data_clear_device_only">فقط پاک کردن دستگاه</string>
<string name="dialog_clear_all_data_clear_device_and_network">پاک کردن دستگاه و شبکه</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">آیا مطمئن هستید که می خواهید داتا های خود را از شبکه حذف کنید؟ اگر ادامه دهید، نمی‌توانید پیام‌ها یا مخاطبین خود را بازیابی کنید.</string>
<string name="dialog_clear_all_data_clear">پاک</string>
<string name="dialog_clear_all_data_local_only">فقط حذف شود</string>

View File

@ -660,8 +660,6 @@
<string name="dialog_clear_all_data_explanation">Cela supprimera définitivement vos messages, vos sessions et vos contacts.</string>
<string name="dialog_clear_all_data_network_explanation">Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ?</string>
<string name="dialog_clear_all_data_message">Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ?</string>
<string name="dialog_clear_all_data_clear_device_only">Effacer l\'appareil uniquement</string>
<string name="dialog_clear_all_data_clear_device_and_network">Effacer l\'appareil et le réseau</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts.</string>
<string name="dialog_clear_all_data_clear">Effacer</string>
<string name="dialog_clear_all_data_local_only">Effacer seulement</string>

View File

@ -660,8 +660,6 @@
<string name="dialog_clear_all_data_explanation">Cela supprimera définitivement vos messages, vos sessions et vos contacts.</string>
<string name="dialog_clear_all_data_network_explanation">Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ?</string>
<string name="dialog_clear_all_data_message">Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ?</string>
<string name="dialog_clear_all_data_clear_device_only">Effacer l\'appareil uniquement</string>
<string name="dialog_clear_all_data_clear_device_and_network">Effacer l\'appareil et le réseau</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts.</string>
<string name="dialog_clear_all_data_clear">Effacer</string>
<string name="dialog_clear_all_data_local_only">Effacer seulement</string>

View File

@ -842,8 +842,6 @@
<string name="dialog_clear_all_data_explanation">This will permanently delete your messages, sessions, and contacts.</string>
<string name="dialog_clear_all_data_network_explanation">Would you like to clear only this device, or delete your entire account?</string>
<string name="dialog_clear_all_data_message">This will permanently delete your messages, sessions, and contacts. Would you like to clear only this device, or delete your entire account?</string>
<string name="dialog_clear_all_data_clear_device_only">Clear Device Only</string>
<string name="dialog_clear_all_data_clear_device_and_network">Clear Device and Network</string>
<string name="dialog_clear_all_data_clear_device_and_network_confirmation">Are you sure you want to delete your data from the network? If you continue you will not be able to restore your messages or contacts.</string>
<string name="dialog_clear_all_data_clear">Clear</string>
<string name="dialog_clear_all_data_local_only">Delete Only</string>

View File

@ -25,7 +25,6 @@ import org.session.libsignal.utilities.Snode
import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.utilities.recover
import org.session.libsignal.utilities.toHexString
import java.util.Date
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.set

View File

@ -73,4 +73,10 @@
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
<!-- RecipientProvider -->
<string name="RecipientProvider_unnamed_group">Unnamed group</string>
<string name="clearDataErrorDescriptionGeneric">An unknown error occurred and your data was not deleted. Do you want to delete your data from just this device instead?</string>
<string name="errorUnknown">An unknown error occurred.</string>
<string name="clearDevice">Clear Device</string>
<string name="clearDeviceOnly">Clear device only</string>
<string name="clearDeviceAndNetwork">Clear device and network</string>
</resources>

View File

@ -4,7 +4,6 @@ import android.os.Process
import kotlinx.coroutines.Dispatchers
import java.util.concurrent.ExecutorService
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.coroutines.EmptyCoroutineContext