From 80100abe7465b8a61acbd5f68cab4045f42a2710 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 12:32:40 +0100 Subject: [PATCH 1/7] Rename .java to .kt Signed-off-by: alperozturk96 --- .../android/utils/{FilesSyncHelper.java => FilesSyncHelper.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/utils/{FilesSyncHelper.java => FilesSyncHelper.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java rename to app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt From 1af8f6b1849b4f751387c876ca4c2e3283e4b490 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 12:32:41 +0100 Subject: [PATCH 2/7] fix(auto-upload): calls Signed-off-by: alperozturk96 --- .../client/jobs/BackgroundJobFactory.kt | 2 +- .../client/jobs/BackgroundJobManager.kt | 4 +- .../client/jobs/BackgroundJobManagerImpl.kt | 40 +------- .../client/jobs/ContentObserverWork.kt | 55 ++--------- .../jobs/autoUpload/AutoUploadWorker.kt | 11 +-- .../notification/WorkerNotificationManager.kt | 11 +++ .../java/com/owncloud/android/MainApp.java | 34 ++++--- .../files/BootupBroadcastReceiver.java | 8 +- .../ui/activity/SyncedFoldersActivity.kt | 6 +- .../owncloud/android/utils/FilesSyncHelper.kt | 99 +++++++------------ 10 files changed, 91 insertions(+), 179 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index 3e67df51fa92..e37bc6e11ac9 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -25,9 +25,9 @@ import com.nextcloud.client.integrations.deck.DeckApi import com.nextcloud.client.jobs.autoUpload.AutoUploadWorker import com.nextcloud.client.jobs.autoUpload.FileSystemRepository import com.nextcloud.client.jobs.download.FileDownloadWorker +import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker import com.nextcloud.client.jobs.metadata.MetadataWorker import com.nextcloud.client.jobs.offlineOperations.OfflineOperationsWorker -import com.nextcloud.client.jobs.folderDownload.FolderDownloadWorker import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.logger.Logger import com.nextcloud.client.network.ConnectivityService 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 abde68ad4040..0dfef42aba27 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt @@ -120,9 +120,7 @@ interface BackgroundJobManager { fun startImmediateFilesExportJob(files: Collection): LiveData - fun schedulePeriodicFilesSyncJob(syncedFolder: SyncedFolder) - - fun startAutoUploadImmediately( + fun startAutoUpload( syncedFolder: SyncedFolder, overridePowerSaving: Boolean = false, contentUris: Array = arrayOf() 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 0ad01e66c7ad..2a8d94c11e99 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -278,11 +278,11 @@ internal class BackgroundJobManagerImpl( .setTriggerContentMaxDelay(MAX_CONTENT_TRIGGER_DELAY_MS, TimeUnit.MILLISECONDS) .build() - val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) + val request = periodicRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) .setConstraints(constrains) .build() - workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request) + workManager.enqueueUniquePeriodicWork(JOB_CONTENT_OBSERVER, ExistingPeriodicWorkPolicy.KEEP, request) } override fun schedulePeriodicContactsBackup(user: User) { @@ -477,40 +477,7 @@ internal class BackgroundJobManagerImpl( ) } - override fun schedulePeriodicFilesSyncJob(syncedFolder: SyncedFolder) { - val syncedFolderID = syncedFolder.id - - val arguments = Data.Builder() - .putLong(AutoUploadWorker.SYNCED_FOLDER_ID, syncedFolderID) - .build() - - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresCharging(syncedFolder.isChargingOnly) - .build() - - val request = periodicRequestBuilder( - jobClass = AutoUploadWorker::class, - jobName = JOB_PERIODIC_FILES_SYNC + "_" + syncedFolderID, - intervalMins = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES, - constraints = constraints - ) - .setBackoffCriteria( - BackoffPolicy.LINEAR, - DEFAULT_BACKOFF_CRITERIA_DELAY_SEC, - TimeUnit.SECONDS - ) - .setInputData(arguments) - .build() - - workManager.enqueueUniquePeriodicWork( - JOB_PERIODIC_FILES_SYNC + "_" + syncedFolderID, - ExistingPeriodicWorkPolicy.KEEP, - request - ) - } - - override fun startAutoUploadImmediately( + override fun startAutoUpload( syncedFolder: SyncedFolder, overridePowerSaving: Boolean, contentUris: Array @@ -533,6 +500,7 @@ internal class BackgroundJobManagerImpl( jobName = JOB_IMMEDIATE_FILES_SYNC + "_" + syncedFolderID ) .setInputData(arguments) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .setConstraints(constraints) .setBackoffCriteria( BackoffPolicy.LINEAR, 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 6d421d23a9d4..ea03c5806b67 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt @@ -7,18 +7,12 @@ */ package com.nextcloud.client.jobs -import android.app.Notification import android.content.Context -import androidx.core.app.NotificationCompat import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.nextcloud.client.device.PowerManagementService -import com.nextcloud.utils.ForegroundServiceHelper -import com.owncloud.android.R -import com.owncloud.android.datamodel.ForegroundServiceType import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.FilesSyncHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -26,13 +20,12 @@ import kotlinx.coroutines.withContext /** * This work is triggered when OS detects change in media folders. * - * It fires media detection job and sync job and finishes immediately. + * It fires media detection worker and auto upload worker and finishes immediately. * - * This job must not be started on API < 24. */ @Suppress("TooGenericExceptionCaught") class ContentObserverWork( - private val context: Context, + context: Context, private val params: WorkerParameters, private val syncedFolderProvider: SyncedFolderProvider, private val powerManagementService: PowerManagementService, @@ -41,8 +34,6 @@ class ContentObserverWork( companion object { private const val TAG = "🔍" + "ContentObserverWork" - private const val CHANNEL_ID = NotificationUtils.NOTIFICATION_CHANNEL_CONTENT_OBSERVER - private const val NOTIFICATION_ID = 774 } override suspend fun doWork(): Result = withContext(Dispatchers.IO) { @@ -53,10 +44,6 @@ class ContentObserverWork( try { if (params.triggeredContentUris.isNotEmpty()) { Log_OC.d(TAG, "📸 content observer detected file changes.") - - val notificationTitle = context.getString(R.string.content_observer_work_notification_title) - val notification = createNotification(notificationTitle) - updateForegroundInfo(notification) checkAndTriggerAutoUpload() // prevent worker fail because of another worker @@ -69,8 +56,6 @@ class ContentObserverWork( Log_OC.d(TAG, "⚠️ triggeredContentUris is empty — nothing to sync.") } - rescheduleSelf() - val result = Result.success() backgroundJobManager.logEndOfWorker(workerName, result) Log_OC.d(TAG, "finished") @@ -81,34 +66,6 @@ class ContentObserverWork( } } - private suspend fun updateForegroundInfo(notification: Notification) { - val foregroundInfo = ForegroundServiceHelper.createWorkerForegroundInfo( - NOTIFICATION_ID, - notification, - ForegroundServiceType.DataSync - ) - setForeground(foregroundInfo) - } - - private fun createNotification(title: String): Notification = NotificationCompat.Builder(context, CHANNEL_ID) - .setContentTitle(title) - .setSmallIcon(R.drawable.ic_find_in_page) - .setOngoing(true) - .setSound(null) - .setVibrate(null) - .setOnlyAlertOnce(true) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setSilent(true) - .build() - - /** - * Re-schedules this observer to ensure continuous monitoring of media changes. - */ - private fun rescheduleSelf() { - Log_OC.d(TAG, "🔁 Rescheduling ContentObserverWork for continued observation.") - backgroundJobManager.scheduleContentObserverJob() - } - private suspend fun checkAndTriggerAutoUpload() = withContext(Dispatchers.IO) { if (powerManagementService.isPowerSavingEnabled) { Log_OC.w(TAG, "⚡ Power saving mode active — skipping file sync.") @@ -124,15 +81,15 @@ class ContentObserverWork( val contentUris = params.triggeredContentUris.map { uri -> // adds uri strings e.g. content://media/external/images/media/2281 uri.toString() - }.toTypedArray() + }.toTypedArray() Log_OC.d(TAG, "📄 Content uris detected") try { - FilesSyncHelper.startAutoUploadImmediatelyWithContentUris( + FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( syncedFolderProvider, backgroundJobManager, - false, - contentUris + contentUris, + false ) 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 db464ccfa11d..c4fb0e4f3b58 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 @@ -186,11 +186,6 @@ class AutoUploadWorker( val currentTime = System.currentTimeMillis() val passedScanInterval = totalScanInterval <= currentTime - Log_OC.d(TAG, "lastScanTimestampMs: " + syncedFolder.lastScanTimestampMs) - Log_OC.d(TAG, "totalScanInterval: $totalScanInterval") - Log_OC.d(TAG, "currentTime: $currentTime") - Log_OC.d(TAG, "passedScanInterval: $passedScanInterval") - if (!passedScanInterval && contentUris.isNullOrEmpty() && !overridePowerSaving) { Log_OC.w( TAG, @@ -199,6 +194,8 @@ class AutoUploadWorker( return true } + Log_OC.d(TAG, "starting ...") + return false } @@ -208,6 +205,8 @@ class AutoUploadWorker( */ @Suppress("MagicNumber", "TooGenericExceptionCaught") private suspend fun collectFileChangesFromContentObserverWork(contentUris: Array?) = try { + Log_OC.d(TAG, "collecting file changes") + withContext(Dispatchers.IO) { if (contentUris.isNullOrEmpty()) { helper.insertEntries(syncedFolder, repository) @@ -289,7 +288,7 @@ class AutoUploadWorker( Log_OC.w(TAG, "no more files to upload at lastId: $lastId") break } - Log_OC.d(TAG, "Processing batch: lastId=$lastId, count=${filePathsWithIds.size}") + Log_OC.d(TAG, "started, processing batch: lastId=$lastId, count=${filePathsWithIds.size}") filePathsWithIds.forEach { (path, id) -> val file = File(path) diff --git a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt index 195a11b72a34..daf5614faf55 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt @@ -89,4 +89,15 @@ open class WorkerNotificationManager( notification, ForegroundServiceType.DataSync ) + + fun createNotification(title: String, iconId: Int): Notification = notificationBuilder + .setContentTitle(title) + .setSmallIcon(iconId) + .setOngoing(true) + .setSound(null) + .setVibrate(null) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setSilent(true) + .build() } diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index e1b946e2781e..15377313d433 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -125,8 +125,8 @@ /** - * Main Application of the project. - * Contains methods to build the "static" strings. These strings were before constants in different classes. + * Main Application of the project. Contains methods to build the "static" strings. These strings were before constants + * in different classes. */ public class MainApp extends Application implements HasAndroidInjector, NetworkChangeListener { public static final OwnCloudVersion OUTDATED_SERVER_VERSION = NextcloudVersion.nextcloud_30; @@ -142,7 +142,7 @@ public class MainApp extends Application implements HasAndroidInjector, NetworkC private static boolean mOnlyOnDevice; private static boolean mOnlyPersonalFiles; - + @Inject protected AppPreferences preferences; @@ -338,6 +338,10 @@ public void onCreate() { } catch (Exception e) { Log_OC.d("Debug", "Failed to disable uri exposure"); } + + Log_OC.d(TAG, "scheduleContentObserverJob, called"); + backgroundJobManager.scheduleContentObserverJob(); + initSyncOperations(this, preferences, uploadsStorageManager, @@ -347,8 +351,7 @@ public void onCreate() { backgroundJobManager, clock, viewThemeUtils, - walledCheckCache, - syncedFolderProvider); + walledCheckCache); initContactsBackup(accountManager, backgroundJobManager); notificationChannels(); @@ -371,9 +374,9 @@ public void onCreate() { if (!MDMConfig.INSTANCE.sendFilesSupport(this)) { disableDocumentsStorageProvider(); } - - - } + + + } public void disableDocumentsStorageProvider() { String packageName = getPackageName(); @@ -386,6 +389,10 @@ public void disableDocumentsStorageProvider() { private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { if (event == Lifecycle.Event.ON_START) { Log_OC.d(TAG, "APP IN FOREGROUND"); + FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider, + backgroundJobManager, + new String[]{}, + true); } else if (event == Lifecycle.Event.ON_STOP) { passCodeManager.setCanAskPin(true); Log_OC.d(TAG, "APP IN BACKGROUND"); @@ -403,7 +410,7 @@ private void setProxyForNonBrandedPlusClients() { Log_OC.d(TAG, "Error caught at setProxyForNonBrandedPlusClients: " + e); } } - + public static boolean isClientBranded() { return getAppContext().getResources().getBoolean(R.bool.is_branded_client); } @@ -415,7 +422,8 @@ public static boolean isClientBrandedPlus() { private final IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() { - @Override public void onReceive(Context context, Intent intent) { + @Override + public void onReceive(Context context, Intent intent) { setProxyConfig(); } }; @@ -605,8 +613,7 @@ public static void initSyncOperations( final BackgroundJobManager backgroundJobManager, final Clock clock, final ViewThemeUtils viewThemeUtils, - final WalledCheckCache walledCheckCache, - final SyncedFolderProvider syncedFolderProvider) { + final WalledCheckCache walledCheckCache) { updateToAutoUpload(context); cleanOldEntries(clock); updateAutoUploadEntries(clock); @@ -620,11 +627,9 @@ public static void initSyncOperations( } if (!preferences.isAutoUploadInitialized()) { - FilesSyncHelper.startAutoUploadImmediately(syncedFolderProvider, backgroundJobManager, false); preferences.setAutoUploadInit(true); } - FilesSyncHelper.scheduleFilesSyncForAllFoldersIfNeeded(appContext.get(), syncedFolderProvider, backgroundJobManager); FilesSyncHelper.restartUploadsIfNeeded( uploadsStorageManager, accountManager, @@ -845,7 +850,6 @@ private static void updateToAutoUpload(Context context) { } } - private static void showAutoUploadAlertDialog(Context context) { new MaterialAlertDialogBuilder(context, R.style.Theme_ownCloud_Dialog) 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 0e367ca7665e..8eb62f17a9c9 100644 --- a/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java +++ b/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java @@ -23,7 +23,6 @@ import com.nextcloud.client.network.WalledCheckCache; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.MainApp; -import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.theme.ViewThemeUtils; @@ -49,7 +48,6 @@ public class BootupBroadcastReceiver extends BroadcastReceiver { @Inject Clock clock; @Inject ViewThemeUtils viewThemeUtils; @Inject WalledCheckCache walledCheckCache; - @Inject SyncedFolderProvider syncedFolderProvider; /** * Receives broadcast intent reporting that the system was just boot up. * @@ -71,9 +69,9 @@ public void onReceive(Context context, Intent intent) { backgroundJobManager, clock, viewThemeUtils, - walledCheckCache, - syncedFolderProvider - ); + walledCheckCache); + Log_OC.d(TAG, "scheduleContentObserverJob, called"); + backgroundJobManager.scheduleContentObserverJob(); MainApp.initContactsBackup(accountManager, backgroundJobManager); } else { Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction()); 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 a25d0a23402c..5dbe190a6266 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 @@ -573,7 +573,7 @@ class SyncedFoldersActivity : } } if (syncedFolderDisplayItem.isEnabled) { - backgroundJobManager.startAutoUploadImmediately(syncedFolderDisplayItem, overridePowerSaving = false) + backgroundJobManager.startAutoUpload(syncedFolderDisplayItem, overridePowerSaving = false) showBatteryOptimizationDialogIfNeeded() } } @@ -736,7 +736,7 @@ class SyncedFoldersActivity : // existing synced folder setup to be updated syncedFolderProvider.updateSyncFolder(item) if (item.isEnabled) { - backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false) + backgroundJobManager.startAutoUpload(item, overridePowerSaving = false) } else { val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id val arbitraryDataProvider = @@ -753,7 +753,7 @@ class SyncedFoldersActivity : if (storedId != -1L) { item.id = storedId if (item.isEnabled) { - backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false) + backgroundJobManager.startAutoUpload(item, overridePowerSaving = false) } else { val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey) 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 60ac53201200..8a2e12274acd 100644 --- a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt +++ b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt @@ -7,72 +7,49 @@ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.utils; - -import android.content.Context; - -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.device.PowerManagementService; -import com.nextcloud.client.jobs.BackgroundJobManager; -import com.nextcloud.client.jobs.upload.FileUploadHelper; -import com.nextcloud.client.network.ConnectivityService; -import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.datamodel.UploadsStorageManager; -import com.owncloud.android.lib.common.utils.Log_OC; - -/** - * Various utilities that make auto upload tick - */ -public final class FilesSyncHelper { - public static final String TAG = "FileSyncHelper"; - - public static final String GLOBAL = "global"; - - private FilesSyncHelper() { - // utility class -> private constructor - } - - public static void restartUploadsIfNeeded(final UploadsStorageManager uploadsStorageManager, - final UserAccountManager accountManager, - final ConnectivityService connectivityService, - final PowerManagementService powerManagementService) { - Log_OC.d(TAG, "restartUploadsIfNeeded, called"); - FileUploadHelper.Companion.instance().retryFailedUploads( +package com.owncloud.android.utils + +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.jobs.BackgroundJobManager +import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.instance +import com.nextcloud.client.network.ConnectivityService +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.datamodel.UploadsStorageManager +import com.owncloud.android.lib.common.utils.Log_OC + +object FilesSyncHelper { + private const val TAG: String = "FileSyncHelper" + const val GLOBAL: String = "global" + + @JvmStatic + fun restartUploadsIfNeeded( + uploadsStorageManager: UploadsStorageManager, + accountManager: UserAccountManager, + connectivityService: ConnectivityService, + powerManagementService: PowerManagementService + ) { + Log_OC.d(TAG, "restartUploadsIfNeeded, called") + instance().retryFailedUploads( uploadsStorageManager, connectivityService, accountManager, - powerManagementService); - } - - public static void scheduleFilesSyncForAllFoldersIfNeeded(Context context, SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager) { - Log_OC.d(TAG, "scheduleFilesSyncForAllFoldersIfNeeded, called"); - for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { - if (syncedFolder.isEnabled()) { - jobManager.schedulePeriodicFilesSyncJob(syncedFolder); - } - } - if (context != null) { - jobManager.scheduleContentObserverJob(); - } else { - Log_OC.w(TAG, "cant scheduleContentObserverJob, context is null"); - } - } - - public static void startAutoUploadImmediatelyWithContentUris(SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager, boolean overridePowerSaving, String[] contentUris) { - Log_OC.d(TAG, "startAutoUploadImmediatelyWithContentUris"); - for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { - if (syncedFolder.isEnabled()) { - jobManager.startAutoUploadImmediately(syncedFolder, overridePowerSaving, contentUris); - } - } + powerManagementService + ) } - public static void startAutoUploadImmediately(SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager, boolean overridePowerSaving) { - Log_OC.d(TAG, "startAutoUploadImmediately"); - for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { - if (syncedFolder.isEnabled()) { - jobManager.startAutoUploadImmediately(syncedFolder, overridePowerSaving, new String[]{}); + @JvmStatic + fun startAutoUploadForEnabledSyncedFolders( + provider: SyncedFolderProvider, + manager: BackgroundJobManager, + uris: Array, + overridePowerSaving: Boolean + ) { + Log_OC.d(TAG, "start auto upload worker for each enabled folder") + + provider.syncedFolders.forEach { + if (it.isEnabled) { + manager.startAutoUpload(it, overridePowerSaving, uris) } } } From 7ad3200b08316496b1c3064418b20c05bcef9413 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 13:13:04 +0100 Subject: [PATCH 3/7] fix(auto-upload): calls Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/BackgroundJobManagerImpl.kt | 3 --- .../client/jobs/autoUpload/AutoUploadHelper.kt | 10 ---------- 2 files changed, 13 deletions(-) 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 2a8d94c11e99..e8d2a79faffa 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -101,8 +101,6 @@ internal class BackgroundJobManagerImpl( const val JOB_TEST = "test_job" - const val MAX_CONTENT_TRIGGER_DELAY_MS = 10000L - const val TAG_PREFIX_NAME = "name" const val TAG_PREFIX_USER = "user" const val TAG_PREFIX_CLASS = "class" @@ -275,7 +273,6 @@ internal class BackgroundJobManagerImpl( .addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) - .setTriggerContentMaxDelay(MAX_CONTENT_TRIGGER_DELAY_MS, TimeUnit.MILLISECONDS) .build() val request = periodicRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt index 15b23e20fbe7..fc1b9d7be6e9 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt @@ -31,16 +31,6 @@ class AutoUploadHelper { } fun insertEntries(folder: SyncedFolder, repository: FileSystemRepository) { - val enabledTimestampMs = folder.enabledTimestampMs - if (!folder.isEnabled || (!folder.isExisting && enabledTimestampMs < 0)) { - Log_OC.w( - TAG, - "Skipping insertDBEntries: enabled=${folder.isEnabled}, " + - "exists=${folder.isExisting}, enabledTs=$enabledTimestampMs" - ) - return - } - when (folder.type) { MediaFolderType.IMAGE -> { repository.insertFromUri(MediaStore.Images.Media.INTERNAL_CONTENT_URI, folder) From 5388fb1b42f038878c656375bcad7234aca699d6 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 13:37:59 +0100 Subject: [PATCH 4/7] fix(auto-upload): calls Signed-off-by: alperozturk96 --- .../com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt | 8 ++++++-- .../java/com/nextcloud/client/jobs/ContentObserverWork.kt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) 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 e8d2a79faffa..46ecb890017c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -43,6 +43,7 @@ import com.owncloud.android.operations.DownloadType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.time.Duration import java.util.Date import java.util.UUID import java.util.concurrent.TimeUnit @@ -267,19 +268,22 @@ internal class BackgroundJobManagerImpl( return workInfo.map { it -> it.map { fromWorkInfo(it) ?: JobInfo() }.sortedBy { it.started }.reversed() } } + @Suppress("MagicNumber") override fun scheduleContentObserverJob() { val constrains = Constraints.Builder() .addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) + .setTriggerContentUpdateDelay(Duration.ofSeconds(5)) + .setTriggerContentUpdateDelay(Duration.ofSeconds(10)) .build() - val request = periodicRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) + val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) .setConstraints(constrains) .build() - workManager.enqueueUniquePeriodicWork(JOB_CONTENT_OBSERVER, ExistingPeriodicWorkPolicy.KEEP, request) + workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request) } override fun schedulePeriodicContactsBackup(user: User) { 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 ea03c5806b67..e691aaf432d8 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt @@ -63,6 +63,9 @@ class ContentObserverWork( } catch (e: Exception) { Log_OC.e(TAG, "❌ Exception in ContentObserverWork: ${e.message}", e) Result.retry() + } finally { + Log_OC.d(TAG, "🔄" + "re-scheduling job") + backgroundJobManager.scheduleContentObserverJob() } } From 979dc1519c2e4d78f63b97e612877c85e1bff375 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 13:38:26 +0100 Subject: [PATCH 5/7] fix(auto-upload): calls Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 46ecb890017c..c5f6ceb021d4 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -276,7 +276,7 @@ internal class BackgroundJobManagerImpl( .addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) .setTriggerContentUpdateDelay(Duration.ofSeconds(5)) - .setTriggerContentUpdateDelay(Duration.ofSeconds(10)) + .setTriggerContentMaxDelay(Duration.ofSeconds(10)) .build() val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER) From 7d13475f820c546e6ca4f6bbc524365dcb26e427 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 28 Jan 2026 13:46:28 +0100 Subject: [PATCH 6/7] fix(auto-upload): calls Signed-off-by: alperozturk96 --- .../jobs/notification/WorkerNotificationManager.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt index daf5614faf55..195a11b72a34 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt @@ -89,15 +89,4 @@ open class WorkerNotificationManager( notification, ForegroundServiceType.DataSync ) - - fun createNotification(title: String, iconId: Int): Notification = notificationBuilder - .setContentTitle(title) - .setSmallIcon(iconId) - .setOngoing(true) - .setSound(null) - .setVibrate(null) - .setOnlyAlertOnce(true) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setSilent(true) - .build() } From c5c82166b9f1f48707dbbd6a84c6968bfba9d692 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 29 Jan 2026 10:19:56 +0100 Subject: [PATCH 7/7] add debounce mechanism to not spam auto upload on start Signed-off-by: alperozturk96 --- .../client/preferences/AppPreferences.java | 3 +++ .../client/preferences/AppPreferencesImpl.java | 18 ++++++++++++++++++ .../java/com/owncloud/android/MainApp.java | 14 ++++++++------ app/src/main/res/values/strings.xml | 1 - 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java index 699b2d927e87..b7d6818ea5a1 100644 --- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java @@ -396,4 +396,7 @@ default void onDarkThemeModeChanged(DarkMode mode) { String getLastDisplayedAccountName(); void setLastDisplayedAccountName(String lastDisplayedAccountName); + + boolean startAutoUploadOnStart(); + void setLastAutoUploadOnStartTime(long timeInMillisecond); } diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java index 682e211fcd5a..a16beaeaaad9 100644 --- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java +++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java @@ -111,6 +111,9 @@ public final class AppPreferencesImpl implements AppPreferences { private static final String PREF_LAST_DISPLAYED_ACCOUNT_NAME = "last_displayed_user"; + private static final String AUTO_PREF__LAST_AUTO_UPLOAD_ON_START = "last_auto_upload_on_start"; + + private static final String LOG_ENTRY = "log_entry"; private final Context context; @@ -118,6 +121,9 @@ public final class AppPreferencesImpl implements AppPreferences { private final UserAccountManager userAccountManager; private final ListenerRegistry listeners; + private static final int AUTO_UPLOAD_ON_START_DEBOUNCE_IN_MINUTES = 10; + private static final long AUTO_UPLOAD_ON_START_DEBOUNCE_MS = AUTO_UPLOAD_ON_START_DEBOUNCE_IN_MINUTES * 60 * 1000L; + /** * Adapter delegating raw {@link SharedPreferences.OnSharedPreferenceChangeListener} calls with key-value pairs to * respective {@link com.nextcloud.client.preferences.AppPreferences.Listener} method. @@ -849,4 +855,16 @@ public String getLastDisplayedAccountName() { public void setLastDisplayedAccountName(String lastDisplayedAccountName) { preferences.edit().putString(PREF_LAST_DISPLAYED_ACCOUNT_NAME, lastDisplayedAccountName).apply(); } + + @Override + public boolean startAutoUploadOnStart() { + long lastRunTime = preferences.getLong(AUTO_PREF__LAST_AUTO_UPLOAD_ON_START, 0L); + long now = System.currentTimeMillis(); + return lastRunTime == 0L || (now - lastRunTime) >= AUTO_UPLOAD_ON_START_DEBOUNCE_MS; + } + + @Override + public void setLastAutoUploadOnStartTime(long timeInMillisecond) { + preferences.edit().putLong(AUTO_PREF__LAST_AUTO_UPLOAD_ON_START, timeInMillisecond).apply(); + } } diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index 15377313d433..bfacbc6d59b8 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -17,7 +17,6 @@ import android.annotation.SuppressLint; import android.app.Activity; -import android.app.ActivityManager; import android.app.Application; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -31,7 +30,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.ConnectivityManager; -import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.StrictMode; @@ -389,10 +387,14 @@ public void disableDocumentsStorageProvider() { private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { if (event == Lifecycle.Event.ON_START) { Log_OC.d(TAG, "APP IN FOREGROUND"); - FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider, - backgroundJobManager, - new String[]{}, - true); + + if (preferences.startAutoUploadOnStart()) { + FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider, + backgroundJobManager, + new String[]{}, + true); + preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis()); + } } else if (event == Lifecycle.Event.ON_STOP) { passCodeManager.setCanAskPin(true); Log_OC.d(TAG, "APP IN BACKGROUND"); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 717f4c5c06a6..15b13412fd3e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -916,7 +916,6 @@ Offline operations Shows progress of offline file operations - Detecting content changes Content observer Detects local file changes