diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt index a859e5808d56..0a4aa4cdd719 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt @@ -35,8 +35,11 @@ interface BackgroundJobManager { * * This call is idempotent - there will be only one scheduled job * regardless of number of calls. + * + * @param overridePowerSaving lets an explicitly user triggered sync run even while the device is in power + * saving mode. The worker reschedules itself without the override afterwards. */ - fun scheduleContentObserverJob() + fun scheduleContentObserverJob(overridePowerSaving: Boolean = false) /** * Schedule periodic contacts backups job. Operating system will diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt index 4a28b1715dea..ef01beddd55b 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -464,7 +464,11 @@ internal class BackgroundJobManagerImpl( } @Suppress("MagicNumber") - override fun scheduleContentObserverJob() { + override fun scheduleContentObserverJob(overridePowerSaving: Boolean) { + val arguments = Data.Builder() + .putBoolean(ContentObserverWork.OVERRIDE_POWER_SAVING, overridePowerSaving) + .build() + val constrains = Constraints.Builder() .addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) @@ -477,6 +481,7 @@ internal class BackgroundJobManagerImpl( val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) .setConstraints(constrains) + .setInputData(arguments) .build() workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request) @@ -508,9 +513,13 @@ internal class BackgroundJobManagerImpl( ) .build() + // an already scheduled run may still carry overridePowerSaving = false and would swallow an explicit + // user request, therefore replace it instead of keeping it + val policy = if (overridePowerSaving) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.KEEP + workManager.enqueueUniqueWork( JOB_IMMEDIATE_FILES_SYNC + "_" + syncedFolderID, - ExistingWorkPolicy.KEEP, + policy, request ) } diff --git a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt index 214b85239b43..6e0f91446a63 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt @@ -36,8 +36,12 @@ class ContentObserverWork( companion object { private const val TAG = "πŸ”" + "ContentObserverWork" + const val OVERRIDE_POWER_SAVING = "overridePowerSaving" } + private val overridePowerSaving: Boolean + get() = inputData.getBoolean(OVERRIDE_POWER_SAVING, false) + override suspend fun doWork(): Result = withContext(Dispatchers.IO) { val workerName = BackgroundJobManagerImpl.formatClassTag(this@ContentObserverWork::class) backgroundJobManager.logStartOfWorker(workerName) @@ -72,7 +76,7 @@ class ContentObserverWork( } private suspend fun checkAndTriggerAutoUpload() = withContext(Dispatchers.IO) { - if (powerManagementService.isPowerSavingEnabled) { + if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) { Log_OC.w(TAG, "⚑ Power saving mode active β€” skipping file sync.") return@withContext } @@ -116,7 +120,7 @@ class ContentObserverWork( FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( syncedFolderProvider, backgroundJobManager, - false + overridePowerSaving ) Log_OC.d(TAG, "βœ… auto upload triggered successfully for ${contentUris.size} file(s).") } catch (e: Exception) { diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index ff897db233d6..b174bd2cb349 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -83,6 +83,9 @@ class AutoUploadWorker( private val fileUploadHelper = FileUploadHelper.instance() private val retryPolicy = UploadDelayPolicy() + private val overridePowerSaving: Boolean + get() = inputData.getBoolean(OVERRIDE_POWER_SAVING, false) + @Suppress("ReturnCount") override suspend fun doWork(): Result { return try { @@ -184,7 +187,6 @@ class AutoUploadWorker( @Suppress("ReturnCount") private suspend fun canExitEarly(syncedFolderID: Long): Boolean { - val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false) if ((powerManagementService.isPowerSavingEnabled && !overridePowerSaving)) { Log_OC.w(TAG, "⚑ Skipping: device is in power saving mode") return true @@ -448,7 +450,11 @@ class AutoUploadWorker( upload.isWhileChargingOnly, true, FileDataStorageManager(user, context.contentResolver) - ) + ).apply { + if (overridePowerSaving) { + isIgnoringPowerSaveMode = true + } + } private fun sendUploadFinishEvent(operation: UploadFileOperation, result: RemoteOperationResult<*>) { fileUploadEventBroadcaster.sendUploadCompleted( diff --git a/app/src/main/java/com/nextcloud/ui/component/UploadWarningCard.kt b/app/src/main/java/com/nextcloud/ui/component/UploadWarningCard.kt index 423f20235840..9152e4c15944 100644 --- a/app/src/main/java/com/nextcloud/ui/component/UploadWarningCard.kt +++ b/app/src/main/java/com/nextcloud/ui/component/UploadWarningCard.kt @@ -17,13 +17,20 @@ import android.provider.Settings import android.view.View import androidx.core.net.toUri import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.utils.extensions.setVisibleIf +import com.owncloud.android.R import com.owncloud.android.databinding.UploadWarningCardBinding +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.FilesSyncHelper import com.owncloud.android.utils.theme.ViewThemeUtils class UploadWarningCard( private val context: Context, private val powerManagementService: PowerManagementService, + private val syncedFolderProvider: SyncedFolderProvider, + private val backgroundJobManager: BackgroundJobManager, private val viewThemeUtils: ViewThemeUtils ) { fun bind(binding: UploadWarningCardBinding) { @@ -34,10 +41,15 @@ class UploadWarningCard( if (isBatterySaver) { viewThemeUtils.material.themeCardView(binding.batterySaverLayout) + viewThemeUtils.material.colorMaterialTextButton(binding.batterySaverButton) + viewThemeUtils.material.colorMaterialTextButton(binding.syncNowButton) binding.batterySaverLayout.visibility = View.VISIBLE binding.batterySaverButton.setOnClickListener { openBatterySaverPage() } + binding.syncNowButton.setOnClickListener { + startAutoUploadViaIgnoringBatteryOptimization(it) + } } else { binding.batterySaverLayout.visibility = View.GONE } @@ -76,6 +88,24 @@ class UploadWarningCard( } // endregion + private fun startAutoUploadViaIgnoringBatteryOptimization(view: View) { + val startedFolderCount = FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + syncedFolderProvider, + backgroundJobManager, + overridePowerSaving = true + ) + + backgroundJobManager.scheduleContentObserverJob(overridePowerSaving = true) + + val message = if (startedFolderCount > 0) { + R.string.auto_upload_sync_now_started + } else { + R.string.auto_upload_sync_now_no_folder + } + + DisplayUtils.showSnackMessage(view, message) + } + /** * Opens page for OS's battery saver screen. */ diff --git a/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt index aaa112d41a8d..5041388ee99c 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt @@ -37,7 +37,7 @@ fun SyncedFolder.shouldSkipFile( } // If "upload existing files" is DISABLED, only upload files created after enabled time - if (!isExisting) { + if (!alsoUploadExistingFiles()) { if (creationTime != null) { if (creationTime < enabledTimestampMs) { Log_OC.d(TAG, "Skipping pre-existing file (creation < enabled): ${file.absolutePath}") @@ -149,7 +149,7 @@ fun SyncedFolder.getLog(): String { πŸ“Ά Wi-Fi only: $isWifiOnly πŸ”Œ Charging only: $isChargingOnly - πŸ“€ Upload existing files: $isExisting + πŸ“€ Upload existing files: ${alsoUploadExistingFiles()} βš™οΈ Upload action: $uploadAction 🧩 Name collision: $nameCollisionPolicy diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index f08bde45fee9..d9971e05ae4c 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -335,7 +335,7 @@ public void onCreate() { } Log_OC.d(TAG, "scheduleContentObserverJob, called"); - backgroundJobManager.scheduleContentObserverJob(); + backgroundJobManager.scheduleContentObserverJob(false); initSyncOperations(this, preferences, diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java index ef7d005f9e82..aa564d9b737c 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java +++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java @@ -201,7 +201,7 @@ public boolean isChargingOnly() { * * @return {@code true} if existing files should also be uploaded, {@code false} otherwise */ - public boolean isExisting() { + public boolean alsoUploadExistingFiles() { return this.existing; } diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.kt b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.kt index 1dae23982037..0e13d030531e 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.kt @@ -208,7 +208,7 @@ class SyncedFolderProvider( put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.remotePath) put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.isWifiOnly) put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.isChargingOnly) - put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING, syncedFolder.isExisting) + put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING, syncedFolder.alsoUploadExistingFiles()) put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled) put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS, syncedFolder.enabledTimestampMs) put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.isSubfolderByDate) diff --git a/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java b/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java index 8eb62f17a9c9..9bb3e0820d07 100644 --- a/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java +++ b/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java @@ -71,7 +71,7 @@ public void onReceive(Context context, Intent intent) { viewThemeUtils, walledCheckCache); Log_OC.d(TAG, "scheduleContentObserverJob, called"); - backgroundJobManager.scheduleContentObserverJob(); + backgroundJobManager.scheduleContentObserverJob(false); MainApp.initContactsBackup(accountManager, backgroundJobManager); } else { Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction()); diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index dd7f88d33588..6bddb5b1a954 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -142,7 +142,7 @@ public class UploadFileOperation extends SyncOperation { private volatile int mCreatedBy; private boolean mOnWifiOnly; private boolean mWhileChargingOnly; - private boolean mIgnoringPowerSaveMode; + private volatile boolean mIgnoringPowerSaveMode; private final boolean mDisableRetries; private volatile boolean mWasRenamed; @@ -293,6 +293,10 @@ public boolean isIgnoringPowerSaveMode() { return mIgnoringPowerSaveMode; } + public void setIgnoringPowerSaveMode(boolean ignoringPowerSaveMode) { + mIgnoringPowerSaveMode = ignoringPowerSaveMode; + } + public User getUser() { return user; } @@ -1003,38 +1007,38 @@ private RemoteOperationResult releaseLocksAndUnlockE2EFolder(FileLock fileLock, } // endregion - private RemoteOperationResult checkConditions(File originalFile) { - RemoteOperationResult remoteOperationResult = null; + private RemoteOperationResult checkConditions(File originalFile) { + RemoteOperationResult remoteOperationResult = null; // check that connectivity conditions are met and delays the upload otherwise Connectivity connectivity = connectivityService.getConnectivity(); if (mOnWifiOnly && (!connectivity.isWifi() || connectivity.isMetered())) { Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath()); - remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI); + remoteOperationResult = new RemoteOperationResult<>(ResultCode.DELAYED_FOR_WIFI); } // check if charging conditions are met and delays the upload otherwise final BatteryStatus battery = powerManagementService.getBattery(); if (mWhileChargingOnly && !battery.isCharging()) { Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath()); - remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING); + remoteOperationResult = new RemoteOperationResult<>(ResultCode.DELAYED_FOR_CHARGING); } // check that device is not in power save mode if (!mIgnoringPowerSaveMode && powerManagementService.isPowerSavingEnabled()) { Log_OC.d(TAG, "Upload delayed because device is in power save mode: " + getRemotePath()); - remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE); + remoteOperationResult = new RemoteOperationResult<>(ResultCode.DELAYED_IN_POWER_SAVE_MODE); } // check if the file continues existing before schedule the operation if (!originalFile.exists()) { Log_OC.d(TAG, mOriginalStoragePath + " does not exist anymore"); - remoteOperationResult = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND); + remoteOperationResult = new RemoteOperationResult<>(ResultCode.LOCAL_FILE_NOT_FOUND); } // check that internet is not behind walled garden if (!connectivityService.getConnectivity().isConnected() || connectivityService.isInternetWalled()) { - remoteOperationResult = new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION); + remoteOperationResult = new RemoteOperationResult<>(ResultCode.NO_NETWORK_CONNECTION); } return remoteOperationResult; diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt index 662b879f0bce..4a8f195aeecb 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt @@ -166,7 +166,13 @@ class SyncedFoldersActivity : super.onCreate(savedInstanceState) binding = SyncedFoldersLayoutBinding.inflate(layoutInflater) setContentView(binding.root) - uploadWarningCard = UploadWarningCard(this, powerManagementService, viewThemeUtils) + uploadWarningCard = UploadWarningCard( + this, + powerManagementService, + syncedFolderProvider, + backgroundJobManager, + viewThemeUtils + ) if (intent != null && intent.extras != null) { val accountName = intent.extras!!.getString(NotificationWork.KEY_NOTIFICATION_ACCOUNT) val optionalUser = user @@ -404,7 +410,7 @@ class SyncedFoldersActivity : syncedFolder.remotePath, syncedFolder.isWifiOnly, syncedFolder.isChargingOnly, - syncedFolder.isExisting, + syncedFolder.alsoUploadExistingFiles(), syncedFolder.isSubfolderByDate, syncedFolder.account, syncedFolder.uploadAction, @@ -436,7 +442,7 @@ class SyncedFoldersActivity : syncedFolder.remotePath, syncedFolder.isWifiOnly, syncedFolder.isChargingOnly, - syncedFolder.isExisting, + syncedFolder.alsoUploadExistingFiles(), syncedFolder.isSubfolderByDate, syncedFolder.account, syncedFolder.uploadAction, @@ -851,7 +857,7 @@ class SyncedFoldersActivity : item.remotePath = remotePath item.isWifiOnly = wifiOnly item.isChargingOnly = chargingOnly - item.isExisting = existing + item.setExisting(existing) item.isSubfolderByDate = subfolderByDate item.uploadAction = uploadAction item.setNameCollisionPolicy(nameCollisionPolicy) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt index b6ef36356a6b..dbec4c9d21fb 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt @@ -90,7 +90,13 @@ class UploadListActivity : binding = UploadListLayoutBinding.inflate(layoutInflater) val binding = binding!! setContentView(binding.getRoot()) - uploadWarningCard = UploadWarningCard(this, powerManagementService, viewThemeUtils) + uploadWarningCard = UploadWarningCard( + this, + powerManagementService, + syncedFolderProvider, + backgroundJobManager, + viewThemeUtils + ) swipeListRefreshLayout = binding.swipeContainingList // this activity has no file really bound, it's for multiple accounts at the same time; should no inherit @@ -174,6 +180,8 @@ class UploadListActivity : accountManager, powerManagementService ) + + loadItems() } override fun onStart() { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index c218bea7d16b..23ff7bc1b8dc 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -629,7 +629,10 @@ class UploadListAdapter( fun loadUploadItemsFromDb(onCompleted: Runnable = {}) { val optionalUser = activity.user val optionalCapabilities = activity.capabilities - if (optionalUser.isEmpty || optionalCapabilities.isEmpty) return + if (optionalUser.isEmpty || optionalCapabilities.isEmpty) { + onCompleted.run() + return + } val accountName = optionalUser.get().accountName val capabilities = optionalCapabilities.get() diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index a8f68a9e0b4a..5e64943f31b4 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -45,7 +45,7 @@ public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, i remotePath = syncedFolderDisplayItem.getRemotePath(); wifiOnly = syncedFolderDisplayItem.isWifiOnly(); chargingOnly = syncedFolderDisplayItem.isChargingOnly(); - existing = syncedFolderDisplayItem.isExisting(); + existing = syncedFolderDisplayItem.alsoUploadExistingFiles(); enabled = syncedFolderDisplayItem.isEnabled(); subfolderByDate = syncedFolderDisplayItem.isSubfolderByDate(); type = syncedFolderDisplayItem.getType(); diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt index 9b18dea58a5b..f20eaeb84208 100644 --- a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt +++ b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt @@ -43,13 +43,12 @@ object FilesSyncHelper { provider: SyncedFolderProvider, manager: BackgroundJobManager, overridePowerSaving: Boolean - ) { + ): Int { Log_OC.d(TAG, "start auto upload worker for each enabled folder") - provider.syncedFolders.forEach { - if (it.isEnabled) { - manager.startAutoUpload(it, overridePowerSaving) - } - } + return provider.syncedFolders + .filter { it.isEnabled } + .onEach { manager.startAutoUpload(it, overridePowerSaving) } + .size } } diff --git a/app/src/main/res/layout/upload_warning_card.xml b/app/src/main/res/layout/upload_warning_card.xml index 4155c8bcaaaa..e5c0a670f548 100644 --- a/app/src/main/res/layout/upload_warning_card.xml +++ b/app/src/main/res/layout/upload_warning_card.xml @@ -58,15 +58,30 @@ tools:visibility="visible" /> - + android:orientation="horizontal"> + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a0f756664301..b868423df80c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1495,6 +1495,9 @@ Nextcloud\'s background activity is limited by battery optimization. Open battery saver settings Allow background activity + Sync now + Auto upload started + No auto upload folder is enabled This will remove the folder and auto-upload configuration. Any unfinished uploads will be canceled. This folder is already included in the parent folder’s sync, which may cause duplicate uploads Sync anyway diff --git a/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt b/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt index 53846287b2db..d62650c281b5 100644 --- a/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt +++ b/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt @@ -10,6 +10,7 @@ package com.nextcloud.client.jobs import android.content.Context import android.net.Uri +import androidx.work.Data import androidx.work.WorkerParameters import com.nextcloud.client.database.dao.FileSystemDao import com.nextcloud.client.device.PowerManagementService @@ -27,6 +28,7 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -70,6 +72,7 @@ class ContentObserverWorkTest { ) val uri: Uri = Mockito.mock(Uri::class.java) whenever(params.triggeredContentUris).thenReturn(listOf(uri)) + whenever(params.inputData).thenReturn(Data.EMPTY) } @After @@ -94,6 +97,34 @@ class ContentObserverWorkTest { } } + @Test + fun power_saving_stops_a_regular_run_before_folders_are_read() { + runBlocking { + whenever(powerManagementService.isPowerSavingEnabled).thenReturn(true) + + worker.doWork() + + verify(folderProvider, never()).countEnabledSyncedFolders() + } + } + + @Test + fun power_saving_does_not_stop_a_run_the_user_asked_for() { + runBlocking { + whenever(powerManagementService.isPowerSavingEnabled).thenReturn(true) + whenever(params.inputData).thenReturn( + Data.Builder() + .putBoolean(ContentObserverWork.OVERRIDE_POWER_SAVING, true) + .build() + ) + whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(0) + + worker.doWork() + + verify(folderProvider).countEnabledSyncedFolders() + } + } + @Test @Ignore("TODO: needs further refactoring") fun sync_is_triggered() { diff --git a/app/src/test/java/com/owncloud/android/utils/FilesSyncHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/FilesSyncHelperTest.kt new file mode 100644 index 000000000000..0ee98ae602dc --- /dev/null +++ b/app/src/test/java/com/owncloud/android/utils/FilesSyncHelperTest.kt @@ -0,0 +1,74 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.utils + +import com.nextcloud.client.jobs.BackgroundJobManager +import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.datamodel.SyncedFolderProvider +import org.junit.Assert.assertEquals +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +class FilesSyncHelperTest { + + private val backgroundJobManager: BackgroundJobManager = mock() + + private fun syncedFolder(enabled: Boolean): SyncedFolder = mock { + on { isEnabled } doReturn enabled + } + + private fun provider(vararg folders: SyncedFolder): SyncedFolderProvider = mock { + on { syncedFolders } doReturn folders.toMutableList() + } + + @Test + fun `sync now starts only enabled folders and asks them to ignore power saving`() { + val enabled = syncedFolder(enabled = true) + val disabled = syncedFolder(enabled = false) + + val startedFolderCount = FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + provider(enabled, disabled), + backgroundJobManager, + overridePowerSaving = true + ) + + assertEquals(1, startedFolderCount) + verify(backgroundJobManager).startAutoUpload(enabled, true) + verify(backgroundJobManager, never()).startAutoUpload(disabled, true) + } + + @Test + fun `scheduled runs keep the power saving check enabled`() { + val enabled = syncedFolder(enabled = true) + + val startedFolderCount = FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + provider(enabled), + backgroundJobManager, + overridePowerSaving = false + ) + + assertEquals(1, startedFolderCount) + verify(backgroundJobManager).startAutoUpload(enabled, false) + } + + @Test + fun `no enabled folder reports nothing to sync`() { + val startedFolderCount = FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + provider(syncedFolder(enabled = false)), + backgroundJobManager, + overridePowerSaving = true + ) + + assertEquals(0, startedFolderCount) + verify(backgroundJobManager, never()).startAutoUpload(any(), any()) + } +}