From cd04eb60f5949e6d181855d00cbf338447ae5093 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 12 Nov 2026 08:34:11 +0100 Subject: [PATCH 1/3] add isWaitingForCharging Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadWorker.kt | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) 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 b2a70f7ed622..a4e42d85da1e 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 @@ -9,6 +9,7 @@ package com.nextcloud.client.jobs.autoUpload import android.app.Notification import android.content.Context +import android.os.BatteryManager import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo @@ -91,8 +92,6 @@ class AutoUploadWorker( override suspend fun doWork(): Result { return try { - trySetForeground() - val syncFolderId = inputData.getLong(SYNCED_FOLDER_ID, -1) syncedFolder = syncedFolderProvider.getSyncedFolderByID(syncFolderId) ?.takeIf { it.isEnabled } ?: return Result.failure() @@ -139,28 +138,13 @@ class AutoUploadWorker( return notificationManager.getForegroundInfo(notification) } - private suspend fun updateNotification() { - getStartNotificationTitle()?.let { (localFolderName, remoteFolderName) -> - try { - val startNotification = createNotification( - context.getString( - R.string.auto_upload_worker_start_text, - localFolderName, - remoteFolderName - ) - ) - - setForeground(notificationManager.getForegroundInfo(startNotification)) - } catch (e: Exception) { - Log_OC.w(TAG, "⚠️ Could not update notification: ${e.message}") - } - } - } - private suspend fun trySetForeground() { + val title = getStartNotificationTitle()?.let { (localFolderName, remoteFolderName) -> + context.getString(R.string.auto_upload_worker_start_text, localFolderName, remoteFolderName) + } ?: context.getString(R.string.upload_files) + try { - val notification = createNotification(context.getString(R.string.upload_files)) - setForeground(notificationManager.getForegroundInfo(notification)) + setForeground(notificationManager.getForegroundInfo(createNotification(title))) } catch (e: Exception) { Log_OC.w(TAG, "⚠️ Could not set foreground service: ${e.message}") } @@ -208,9 +192,25 @@ class AutoUploadWorker( return true } + if (isWaitingForCharging()) { + Log_OC.w(TAG, "🔌 Skipping: charging only folder but battery is not charging") + return true + } + return false } + /** + * JobScheduler counts "plugged in at the charge limit" as charging, WorkManager only accepts + * `status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL`, see `isBatteryChangedIntentCharging()` in + * [androidx.work.impl.constraints.trackers.BatteryChargingTracker]. + * + * Foreground work gets its constraints re-checked by WorkManager, which stops it, and JobScheduler restarts it at + * once, in a loop, see [androidx.work.impl.foreground.SystemForegroundDispatcher.onConstraintsStateChanged]. + */ + private fun isWaitingForCharging(): Boolean = syncedFolder.isChargingOnly && + context.getSystemService(BatteryManager::class.java)?.isCharging != true + private suspend fun hasNothingToDo(): Boolean { if (repository.hasPendingFiles(syncedFolder)) { Log_OC.d(TAG, "pending files found, starting...") @@ -272,7 +272,7 @@ class AutoUploadWorker( .getClientFor(ocAccount, context) val capability = CapabilityUtils.getCapability(user, context) - updateNotification() + trySetForeground() var lastId = 0 From 0cd6357c19b656483d55be8b7751bf2e714eda5e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 13 Nov 2026 16:09:50 +0100 Subject: [PATCH 2/3] remove confusion is charging Signed-off-by: alperozturk96 --- .../com/nextcloud/client/device/BatteryStatus.kt | 4 ++-- .../client/device/PowerManagementServiceImpl.kt | 4 ++-- .../nextcloud/client/jobs/upload/FileUploadHelper.kt | 2 +- .../android/operations/UploadFileOperation.java | 2 +- .../client/device/TestPowerManagementService.kt | 12 ++++++------ 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt b/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt index a94102630bab..446335051869 100644 --- a/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt +++ b/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt @@ -10,12 +10,12 @@ package com.nextcloud.client.device * This class exposes battery status information * in platform-independent way. * - * @param isCharging true if device battery is charging + * @param isPluggedIn true if device is connected to a power source, even when the battery is not charging * @param level Battery level, from 0 to 100% * * @see [android.os.BatteryManager] */ -data class BatteryStatus(val isCharging: Boolean = false, val level: Int = 0) { +data class BatteryStatus(val isPluggedIn: Boolean = false, val level: Int = 0) { companion object { const val BATTERY_FULL = 100 diff --git a/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt b/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt index d28943b1849d..783c36e071a7 100644 --- a/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt +++ b/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt @@ -49,7 +49,7 @@ internal class PowerManagementServiceImpl( IntentFilter(Intent.ACTION_BATTERY_CHANGED), ReceiverFlag.NotExported ) - val isCharging = intent?.let { + val isPluggedIn = intent?.let { when (it.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)) { BatteryManager.BATTERY_PLUGGED_USB -> true BatteryManager.BATTERY_PLUGGED_AC -> true @@ -62,6 +62,6 @@ internal class PowerManagementServiceImpl( val scale: Int = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1) (level * 100 / scale.toFloat()).toInt() } ?: 0 - return BatteryStatus(isCharging, level) + return BatteryStatus(isPluggedIn, level) } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 0203dc81dd54..eedf27e16fef 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -463,7 +463,7 @@ class FileUploadHelper { } // check if charging conditions are met; delay upload otherwise - if (upload.isWhileChargingOnly && !battery.isCharging && !battery.isFull) { + if (upload.isWhileChargingOnly && !battery.isPluggedIn && !battery.isFull) { conditions = UploadResult.DELAYED_FOR_CHARGING } 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 8ed1907a113f..efec7b2e3508 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1004,7 +1004,7 @@ private RemoteOperationResult checkConditions(File originalFile) { // check if charging conditions are met and delays the upload otherwise final BatteryStatus battery = powerManagementService.getBattery(); - if (mWhileChargingOnly && !battery.isCharging()) { + if (mWhileChargingOnly && !battery.isPluggedIn()) { Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath()); remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING); } diff --git a/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt b/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt index 87d1ec7c0418..ae57faf4eee1 100644 --- a/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt +++ b/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt @@ -140,7 +140,7 @@ class TestPowerManagementService { } @Test - fun `battery charging status`() { + fun `battery plugged in status`() { // GIVEN // battery status sticky intent is available whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.P) @@ -152,13 +152,13 @@ class TestPowerManagementService { for (row in powerSources) { // WHEN - // device is charging using supported power source + // device is plugged in to supported power source whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any())) .thenReturn(row) // THEN - // charging flag is true - assertTrue(powerManagementService.battery.isCharging) + // plugged in flag is true + assertTrue(powerManagementService.battery.isPluggedIn) } } @@ -173,8 +173,8 @@ class TestPowerManagementService { ) // THEN - // charging flag is false - assertFalse(powerManagementService.battery.isCharging) + // plugged in flag is false + assertFalse(powerManagementService.battery.isPluggedIn) verify(context).registerReceiver(anyOrNull(), any()) } From 007de7137915c0c5d70656d5855d35d330c56b15 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 13 Nov 2026 16:14:27 +0100 Subject: [PATCH 3/3] remove confusion is charging Signed-off-by: alperozturk96 --- .../nextcloud/client/device/BatteryStatus.kt | 11 ++------- .../device/PowerManagementServiceImpl.kt | 11 ++++----- .../jobs/autoUpload/AutoUploadWorker.kt | 13 ++--------- .../client/jobs/upload/FileUploadHelper.kt | 2 +- .../operations/UploadFileOperation.java | 2 +- .../device/TestPowerManagementService.kt | 23 +++++++++---------- 6 files changed, 22 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt b/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt index 446335051869..7e231616413d 100644 --- a/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt +++ b/app/src/main/java/com/nextcloud/client/device/BatteryStatus.kt @@ -10,21 +10,14 @@ package com.nextcloud.client.device * This class exposes battery status information * in platform-independent way. * - * @param isPluggedIn true if device is connected to a power source, even when the battery is not charging + * @param isCharging true if the battery is charging or full, false when plugged in but held at a charge limit * @param level Battery level, from 0 to 100% * * @see [android.os.BatteryManager] */ -data class BatteryStatus(val isPluggedIn: Boolean = false, val level: Int = 0) { +data class BatteryStatus(val isCharging: Boolean = false, val level: Int = 0) { companion object { const val BATTERY_FULL = 100 } - - /** - * True if battery is fully loaded, false otherwise. - * Some dodgy devices can report battery charging - * status as "battery full". - */ - val isFull: Boolean get() = level >= BATTERY_FULL } diff --git a/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt b/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt index 783c36e071a7..4ec2b6f1abca 100644 --- a/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt +++ b/app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt @@ -49,11 +49,10 @@ internal class PowerManagementServiceImpl( IntentFilter(Intent.ACTION_BATTERY_CHANGED), ReceiverFlag.NotExported ) - val isPluggedIn = intent?.let { - when (it.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)) { - BatteryManager.BATTERY_PLUGGED_USB -> true - BatteryManager.BATTERY_PLUGGED_AC -> true - BatteryManager.BATTERY_PLUGGED_WIRELESS -> true + val isCharging = intent?.let { + when (it.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)) { + BatteryManager.BATTERY_STATUS_CHARGING -> true + BatteryManager.BATTERY_STATUS_FULL -> true else -> false } } ?: false @@ -62,6 +61,6 @@ internal class PowerManagementServiceImpl( val scale: Int = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1) (level * 100 / scale.toFloat()).toInt() } ?: 0 - return BatteryStatus(isPluggedIn, level) + return BatteryStatus(isCharging, level) } } 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 a4e42d85da1e..004ed470144b 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 @@ -9,7 +9,6 @@ package com.nextcloud.client.jobs.autoUpload import android.app.Notification import android.content.Context -import android.os.BatteryManager import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo @@ -200,16 +199,8 @@ class AutoUploadWorker( return false } - /** - * JobScheduler counts "plugged in at the charge limit" as charging, WorkManager only accepts - * `status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL`, see `isBatteryChangedIntentCharging()` in - * [androidx.work.impl.constraints.trackers.BatteryChargingTracker]. - * - * Foreground work gets its constraints re-checked by WorkManager, which stops it, and JobScheduler restarts it at - * once, in a loop, see [androidx.work.impl.foreground.SystemForegroundDispatcher.onConstraintsStateChanged]. - */ - private fun isWaitingForCharging(): Boolean = syncedFolder.isChargingOnly && - context.getSystemService(BatteryManager::class.java)?.isCharging != true + private fun isWaitingForCharging(): Boolean = + syncedFolder.isChargingOnly && !powerManagementService.battery.isCharging private suspend fun hasNothingToDo(): Boolean { if (repository.hasPendingFiles(syncedFolder)) { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index eedf27e16fef..70b691f0bce9 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -463,7 +463,7 @@ class FileUploadHelper { } // check if charging conditions are met; delay upload otherwise - if (upload.isWhileChargingOnly && !battery.isPluggedIn && !battery.isFull) { + if (upload.isWhileChargingOnly && !battery.isCharging) { conditions = UploadResult.DELAYED_FOR_CHARGING } 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 efec7b2e3508..8ed1907a113f 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1004,7 +1004,7 @@ private RemoteOperationResult checkConditions(File originalFile) { // check if charging conditions are met and delays the upload otherwise final BatteryStatus battery = powerManagementService.getBattery(); - if (mWhileChargingOnly && !battery.isPluggedIn()) { + if (mWhileChargingOnly && !battery.isCharging()) { Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath()); remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING); } diff --git a/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt b/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt index ae57faf4eee1..8d0aeba853ca 100644 --- a/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt +++ b/app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt @@ -140,25 +140,24 @@ class TestPowerManagementService { } @Test - fun `battery plugged in status`() { + fun `battery charging status`() { // GIVEN // battery status sticky intent is available whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.P) - val powerSources = setOf( - BatteryManager.BATTERY_PLUGGED_AC, - BatteryManager.BATTERY_PLUGGED_USB, - BatteryManager.BATTERY_PLUGGED_WIRELESS + val chargingStatuses = setOf( + BatteryManager.BATTERY_STATUS_CHARGING, + BatteryManager.BATTERY_STATUS_FULL ) - for (row in powerSources) { + for (row in chargingStatuses) { // WHEN - // device is plugged in to supported power source - whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any())) + // battery is charging or full + whenever(intent.getIntExtra(eq(BatteryManager.EXTRA_STATUS), any())) .thenReturn(row) // THEN - // plugged in flag is true - assertTrue(powerManagementService.battery.isPluggedIn) + // charging flag is true + assertTrue(powerManagementService.battery.isCharging) } } @@ -173,8 +172,8 @@ class TestPowerManagementService { ) // THEN - // plugged in flag is false - assertFalse(powerManagementService.battery.isPluggedIn) + // charging flag is false + assertFalse(powerManagementService.battery.isCharging) verify(context).registerReceiver(anyOrNull(), any()) }