diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveRestoreProgress.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveRestoreProgress.kt index d786563f1b..ac6bf9bd93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveRestoreProgress.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveRestoreProgress.kt @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.DatabaseObserver import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobmanager.impl.BatteryNotLowConstraint +import org.thoughtcrime.securesms.jobmanager.impl.DiskSpaceNotLowConstraint import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint import org.thoughtcrime.securesms.keyvalue.SignalStore @@ -185,6 +186,7 @@ object ArchiveRestoreProgress { !WifiConstraint.isMet(AppDependencies.application) && !SignalStore.backup.restoreWithCellular -> ArchiveRestoreProgressState.RestoreStatus.WAITING_FOR_WIFI !NetworkConstraint.isMet(AppDependencies.application) -> ArchiveRestoreProgressState.RestoreStatus.WAITING_FOR_INTERNET !BatteryNotLowConstraint.isMet() -> ArchiveRestoreProgressState.RestoreStatus.LOW_BATTERY + !DiskSpaceNotLowConstraint.isMet() -> ArchiveRestoreProgressState.RestoreStatus.NOT_ENOUGH_DISK_SPACE restoreState == RestoreState.NONE -> if (state.hasActivelyRestoredThisRun) ArchiveRestoreProgressState.RestoreStatus.FINISHED else ArchiveRestoreProgressState.RestoreStatus.NONE else -> { val availableBytes = SignalStore.backup.spaceAvailableOnDiskBytes diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index dfd92e17e6..2ecc342b9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -7,8 +7,6 @@ package org.thoughtcrime.securesms.backup.v2 import android.app.PendingIntent import android.database.Cursor -import android.os.Environment -import android.os.StatFs import androidx.annotation.CheckResult import androidx.annotation.Discouraged import androidx.annotation.WorkerThread @@ -22,7 +20,6 @@ import okio.ByteString.Companion.toByteString import org.greenrobot.eventbus.EventBus import org.signal.core.util.Base64 import org.signal.core.util.Base64.decodeBase64OrThrow -import org.signal.core.util.ByteSize import org.signal.core.util.CursorUtil import org.signal.core.util.DiskUtil import org.signal.core.util.EventTimer @@ -361,16 +358,6 @@ object BackupRepository { } } - /** - * Gets the free storage space in the device's data partition. - */ - fun getFreeStorageSpace(): ByteSize { - val statFs = StatFs(Environment.getDataDirectory().absolutePath) - val free = (statFs.availableBlocksLong) * statFs.blockSizeLong - - return free.bytes - } - /** * Checks whether or not we do not have enough storage space for our remaining attachments to be downloaded. * Caller from the attachment / thumbnail download jobs. diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DiskSpaceNotLowConstraint.kt b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DiskSpaceNotLowConstraint.kt new file mode 100644 index 0000000000..8a46dab786 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DiskSpaceNotLowConstraint.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.jobmanager.impl + +import android.app.job.JobInfo +import android.os.Build +import org.signal.core.util.DiskUtil +import org.signal.core.util.mebiBytes +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.jobmanager.Constraint +import kotlin.time.Duration.Companion.seconds + +/** + * A constraint that is met so long as there is [MINIMUM_DISK_SPACE_BYTES] of space remaining on the user's disk. + */ +object DiskSpaceNotLowConstraint : Constraint { + + const val KEY = "DiskSpaceAvailableConstraint" + + private val MINIMUM_DISK_SPACE_BYTES = 100.mebiBytes.inWholeBytes + private val CHECK_INTERVAL = 5.seconds.inWholeMilliseconds + + private var lastKnownBytesRemaining: Long = Long.MAX_VALUE + private var lastCheckTime: Long = 0 + + override fun isMet(): Boolean { + if (System.currentTimeMillis() - lastCheckTime >= CHECK_INTERVAL) { + lastKnownBytesRemaining = DiskUtil.getAvailableSpace(AppDependencies.application).inWholeBytes + lastCheckTime = System.currentTimeMillis() + } + + return lastKnownBytesRemaining > MINIMUM_DISK_SPACE_BYTES + } + + override fun getFactoryKey(): String = KEY + + override fun applyToJobInfo(jobInfoBuilder: JobInfo.Builder) { + if (Build.VERSION.SDK_INT >= 26) { + jobInfoBuilder.setRequiresStorageNotLow(true) + } + } + + class Factory() : Constraint.Factory { + override fun create(): DiskSpaceNotLowConstraint { + return DiskSpaceNotLowConstraint + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index f0813e605f..c9a7b516e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.DataRestoreConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint; import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.DeletionNotAwaitingMediaDownloadConstraint; +import org.thoughtcrime.securesms.jobmanager.impl.DiskSpaceNotLowConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; @@ -430,6 +431,7 @@ public final class JobManagerFactories { put(DataRestoreConstraint.KEY, new DataRestoreConstraint.Factory()); put(DecryptionsDrainedConstraint.KEY, new DecryptionsDrainedConstraint.Factory()); put(DeletionNotAwaitingMediaDownloadConstraint.KEY, new DeletionNotAwaitingMediaDownloadConstraint.Factory()); + put(DiskSpaceNotLowConstraint.KEY, new DiskSpaceNotLowConstraint.Factory()); put(NetworkConstraint.KEY, new NetworkConstraint.Factory(application)); put(NetworkOrCellServiceConstraint.KEY, new NetworkOrCellServiceConstraint.Factory(application)); put(NetworkOrCellServiceConstraint.LEGACY_KEY, new NetworkOrCellServiceConstraint.Factory(application)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt index 24efd32e47..43d18bc299 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RestoreAttachmentJob.kt @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.JobLogger.format import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil import org.thoughtcrime.securesms.jobmanager.impl.BatteryNotLowConstraint +import org.thoughtcrime.securesms.jobmanager.impl.DiskSpaceNotLowConstraint import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraint import org.thoughtcrime.securesms.jobmanager.impl.StickersNotDownloadingConstraint @@ -204,6 +205,7 @@ class RestoreAttachmentJob private constructor( } else { addConstraint(RestoreAttachmentConstraint.KEY) addConstraint(BatteryNotLowConstraint.KEY) + addConstraint(DiskSpaceNotLowConstraint.KEY) } if (stickerPackId != null && SignalDatabase.stickers.isPackInstalled(stickerPackId)) {