mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Fix ANR when backup deletion hangs.
This commit is contained in:
committed by
Greyson Parrelli
parent
20d16a8433
commit
bd4ce1788c
@@ -13,8 +13,10 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
@@ -29,6 +31,7 @@ import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.navigationevent.compose.LocalNavigationEventDispatcherOwner
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Launchers
|
||||
import org.signal.core.ui.util.StorageUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
@@ -39,6 +42,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRec
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordScreen
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyVerifyScreen
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
private val TAG = Log.tag(LocalBackupsFragment::class)
|
||||
|
||||
@@ -127,6 +131,7 @@ class LocalBackupsFragment : ComposeFragment() {
|
||||
val state: LocalBackupsKeyState by viewModel.backupState.collectAsStateWithLifecycle()
|
||||
val scope = rememberCoroutineScope()
|
||||
val backupKeyUpdatedMessage = stringResource(R.string.OnDeviceBackupsFragment__backup_key_updated)
|
||||
var upgradeInProgress by remember { mutableStateOf(false) }
|
||||
|
||||
MessageBackupsKeyVerifyScreen(
|
||||
backupKey = state.accountEntropyPool.displayValue,
|
||||
@@ -139,7 +144,9 @@ class LocalBackupsFragment : ComposeFragment() {
|
||||
backstack.removeAll { it != LocalBackupsNavKey.SETTINGS }
|
||||
|
||||
scope.launch {
|
||||
upgradeInProgress = true
|
||||
viewModel.handleUpgrade(requireContext())
|
||||
upgradeInProgress = false
|
||||
|
||||
snackbarHostState.showSnackbar(
|
||||
message = backupKeyUpdatedMessage
|
||||
@@ -147,6 +154,12 @@ class LocalBackupsFragment : ComposeFragment() {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Dialogs.IndeterminateProgressDialog(
|
||||
visible = upgradeInProgress,
|
||||
delayDuration = 100.milliseconds,
|
||||
minimumDisplayDuration = 500.milliseconds
|
||||
)
|
||||
}
|
||||
|
||||
else -> error("Unknown key: $key")
|
||||
|
||||
@@ -168,13 +168,13 @@ class LocalBackupsViewModel : ViewModel(), BackupKeyCredentialManagerHandler {
|
||||
withContext(Dispatchers.IO) {
|
||||
AppDependencies.jobManager.cancelAllInQueue(LocalBackupJob.QUEUE)
|
||||
AppDependencies.jobManager.flush()
|
||||
|
||||
SignalStore.backup.newLocalBackupsDirectory = SignalStore.settings.signalBackupDirectory?.toString()
|
||||
|
||||
BackupPassphrase.set(context, null)
|
||||
SignalStore.settings.isBackupEnabled = false
|
||||
BackupUtil.deleteAllBackups()
|
||||
}
|
||||
|
||||
SignalStore.backup.newLocalBackupsDirectory = SignalStore.settings.signalBackupDirectory?.toString()
|
||||
|
||||
BackupPassphrase.set(context, null)
|
||||
SignalStore.settings.isBackupEnabled = false
|
||||
BackupUtil.deleteAllBackups()
|
||||
}
|
||||
|
||||
SignalStore.backup.newLocalBackupsEnabled = true
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
@@ -110,6 +111,7 @@ public class BackupUtil {
|
||||
return backups.isEmpty() ? null : backups.get(0);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void deleteAllBackups() {
|
||||
Log.i(TAG, "Deleting all backups");
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -56,12 +58,15 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import kotlinx.coroutines.delay
|
||||
import org.signal.core.ui.compose.Dialogs.AdvancedAlertDialog
|
||||
import org.signal.core.ui.compose.Dialogs.PermissionRationaleDialog
|
||||
import org.signal.core.ui.compose.Dialogs.SimpleAlertDialog
|
||||
import org.signal.core.ui.compose.Dialogs.SimpleMessageDialog
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object Dialogs {
|
||||
|
||||
@@ -225,6 +230,49 @@ object Dialogs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dialog that shows a spinner with built-in delay and minimum display time.
|
||||
*
|
||||
* The dialog will not appear until [delayDuration] has elapsed after [visible] becomes true.
|
||||
* If the operation completes before the delay, the dialog is never shown.
|
||||
* Once visible, the dialog will remain for at least [minimumDisplayDuration] to
|
||||
* avoid a jarring flash.
|
||||
*
|
||||
* This composable should always be in the composition (not wrapped in an `if`).
|
||||
* Visibility is controlled by the [visible] parameter.
|
||||
*/
|
||||
@Composable
|
||||
fun IndeterminateProgressDialog(
|
||||
visible: Boolean,
|
||||
delayDuration: Duration = Duration.ZERO,
|
||||
minimumDisplayDuration: Duration = Duration.ZERO,
|
||||
onDismissRequest: () -> Unit = {}
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(false) }
|
||||
var isVisibleSince by remember { mutableLongStateOf(0L) }
|
||||
|
||||
LaunchedEffect(visible) {
|
||||
if (visible) {
|
||||
delay(delayDuration)
|
||||
isVisible = true
|
||||
isVisibleSince = System.currentTimeMillis()
|
||||
} else {
|
||||
if (isVisible && minimumDisplayDuration > Duration.ZERO) {
|
||||
val elapsed = (System.currentTimeMillis() - isVisibleSince).milliseconds
|
||||
val remaining = minimumDisplayDuration - elapsed
|
||||
if (remaining > Duration.ZERO) {
|
||||
delay(remaining)
|
||||
}
|
||||
}
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
IndeterminateProgressDialog(onDismissRequest = onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizable progress spinner that shows [message] below the spinner to let users know
|
||||
* an action is completing
|
||||
|
||||
Reference in New Issue
Block a user