فهرست منبع

Cleanup code

Signed-off-by: alperozturk <alper_ozturk@proton.me>
alperozturk 1 سال پیش
والد
کامیت
cf09356cf7

+ 39 - 0
app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadHelper.kt

@@ -24,9 +24,13 @@ package com.nextcloud.client.files.downloader
 import com.nextcloud.client.account.User
 import com.nextcloud.client.jobs.BackgroundJobManager
 import com.owncloud.android.MainApp
+import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.operations.DownloadFileOperation
 import com.owncloud.android.operations.DownloadType
+import com.owncloud.android.utils.MimeTypeUtil
+import java.io.File
 import javax.inject.Inject
 
 class FilesDownloadHelper {
@@ -41,6 +45,40 @@ class FilesDownloadHelper {
         MainApp.getAppComponent().inject(this)
     }
 
+    fun saveFile(
+        file: OCFile,
+        currentDownload: DownloadFileOperation?,
+        storageManager: FileDataStorageManager?
+    ) {
+        val syncDate = System.currentTimeMillis()
+
+        file.apply {
+            lastSyncDateForProperties = syncDate
+            lastSyncDateForData = syncDate
+            isUpdateThumbnailNeeded = true
+            modificationTimestamp = currentDownload?.modificationTimestamp ?: 0L
+            modificationTimestampAtLastSyncForData = currentDownload?.modificationTimestamp ?: 0L
+            etag = currentDownload?.etag
+            mimeType = currentDownload?.mimeType
+            storagePath = currentDownload?.savePath
+
+            val savePathFile = currentDownload?.savePath?.let { File(it) }
+            savePathFile?.let {
+                fileLength = savePathFile.length()
+            }
+
+            remoteId = currentDownload?.file?.remoteId
+        }
+
+        storageManager?.saveFile(file)
+
+        if (MimeTypeUtil.isMedia(currentDownload?.mimeType)) {
+            FileDataStorageManager.triggerMediaScan(file.storagePath, file)
+        }
+
+        storageManager?.saveConflict(file, null)
+    }
+
     fun downloadFile(user: User, ocFile: OCFile) {
         backgroundJobManager.startFilesDownloadJob(
             user,
@@ -89,6 +127,7 @@ class FilesDownloadHelper {
         )
     }
 
+    @Suppress("LongParameterList")
     fun downloadFile(
         user: User,
         ocFile: OCFile,

+ 83 - 0
app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadIntents.kt

@@ -0,0 +1,83 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.files.downloader
+
+import android.content.Context
+import android.content.Intent
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.operations.DownloadFileOperation
+import com.owncloud.android.ui.activity.FileActivity
+import com.owncloud.android.ui.activity.FileDisplayActivity
+import com.owncloud.android.ui.dialog.SendShareDialog
+import com.owncloud.android.ui.fragment.OCFileListFragment
+import com.owncloud.android.ui.preview.PreviewImageActivity
+import com.owncloud.android.ui.preview.PreviewImageFragment
+
+class FilesDownloadIntents(private val context: Context) {
+
+    fun newDownloadIntent(
+        download: DownloadFileOperation,
+        linkedToRemotePath: String
+    ): Intent {
+        return Intent(FilesDownloadWorker.getDownloadAddedMessage()).apply {
+            putExtra(FilesDownloadWorker.ACCOUNT_NAME, download.user.accountName)
+            putExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH, download.remotePath)
+            putExtra(FilesDownloadWorker.EXTRA_LINKED_TO_PATH, linkedToRemotePath)
+            setPackage(context.packageName)
+        }
+    }
+
+    fun downloadFinishedIntent(
+        download: DownloadFileOperation,
+        downloadResult: RemoteOperationResult<*>,
+        unlinkedFromRemotePath: String?
+    ): Intent {
+        return Intent(FilesDownloadWorker.getDownloadFinishMessage()).apply {
+            putExtra(FilesDownloadWorker.EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess)
+            putExtra(FilesDownloadWorker.ACCOUNT_NAME, download.user.accountName)
+            putExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH, download.remotePath)
+            putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.behaviour)
+            putExtra(SendShareDialog.ACTIVITY_NAME, download.activityName)
+            putExtra(SendShareDialog.PACKAGE_NAME, download.packageName)
+            if (unlinkedFromRemotePath != null) {
+                putExtra(FilesDownloadWorker.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
+            }
+            setPackage(context.packageName)
+        }
+    }
+
+    fun detailsIntent(operation: DownloadFileOperation?): Intent {
+        return if (operation != null) {
+            if (PreviewImageFragment.canBePreviewed(operation.file)) {
+                Intent(context, PreviewImageActivity::class.java)
+            } else {
+                Intent(context, FileDisplayActivity::class.java)
+            }.apply {
+                putExtra(FileActivity.EXTRA_FILE, operation.file)
+                putExtra(FileActivity.EXTRA_USER, operation.user)
+                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+            }
+        } else {
+            Intent()
+        }
+    }
+}

+ 36 - 237
app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadWorker.kt

@@ -24,26 +24,19 @@ package com.nextcloud.client.files.downloader
 import android.accounts.Account
 import android.accounts.AccountManager
 import android.accounts.OnAccountsUpdateListener
-import android.app.Notification
-import android.app.NotificationManager
 import android.app.PendingIntent
 import android.content.Context
-import android.content.Intent
-import android.graphics.BitmapFactory
 import android.os.Binder
-import android.os.Build
 import android.os.IBinder
 import android.util.Pair
-import androidx.core.app.NotificationCompat
 import androidx.localbroadcastmanager.content.LocalBroadcastManager
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import com.google.gson.Gson
 import com.nextcloud.client.account.User
 import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.notifications.download.DownloadNotificationManager
 import com.nextcloud.java.util.Optional
-import com.owncloud.android.R
-import com.owncloud.android.authentication.AuthenticatorActivity
 import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.UploadsStorageManager
@@ -55,31 +48,20 @@ import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
 import com.owncloud.android.lib.common.operations.RemoteOperationResult
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
 import com.owncloud.android.lib.common.utils.Log_OC
-import com.owncloud.android.lib.resources.files.FileUtils
 import com.owncloud.android.operations.DownloadFileOperation
 import com.owncloud.android.operations.DownloadType
-import com.owncloud.android.ui.activity.FileActivity
-import com.owncloud.android.ui.activity.FileDisplayActivity
-import com.owncloud.android.ui.dialog.SendShareDialog
-import com.owncloud.android.ui.fragment.OCFileListFragment
-import com.owncloud.android.ui.notifications.NotificationUtils
-import com.owncloud.android.ui.preview.PreviewImageActivity
-import com.owncloud.android.ui.preview.PreviewImageFragment
-import com.owncloud.android.utils.ErrorMessageAdapter
-import com.owncloud.android.utils.MimeTypeUtil
 import com.owncloud.android.utils.theme.ViewThemeUtils
-import java.io.File
-import java.security.SecureRandom
 import java.util.AbstractList
 import java.util.Vector
 
+@Suppress("LongParameterList")
 class FilesDownloadWorker(
     private val viewThemeUtils: ViewThemeUtils,
     private val accountManager: UserAccountManager,
     private val uploadsStorageManager: UploadsStorageManager,
     private var localBroadcastManager: LocalBroadcastManager,
     private val context: Context,
-    params: WorkerParameters,
+    params: WorkerParameters
 ) : Worker(context, params), OnAccountsUpdateListener, OnDatatransferProgressListener {
 
     companion object {
@@ -108,25 +90,26 @@ class FilesDownloadWorker(
         }
     }
 
-    private var notification: Notification? = null
     private var currentDownload: DownloadFileOperation? = null
     private var conflictUploadId: Long? = null
     private var lastPercent = 0
-    private lateinit var notificationBuilder: NotificationCompat.Builder
-    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+    private val intents = FilesDownloadIntents(context)
+    private val notificationManager = DownloadNotificationManager(context, viewThemeUtils)
     private val pendingDownloads = IndexedForest<DownloadFileOperation>()
     private var downloadBinder: IBinder = FileDownloaderBinder()
     private var currentUser = Optional.empty<User>()
+    private val helper = FilesDownloadHelper()
     private var startedDownload = false
     private var storageManager: FileDataStorageManager? = null
     private var downloadClient: OwnCloudClient? = null
     private val gson = Gson()
 
+    @Suppress("TooGenericExceptionCaught")
     override fun doWork(): Result {
         return try {
             val requestDownloads = getRequestDownloads()
 
-            initNotificationBuilder()
+            notificationManager.init()
             addAccountUpdateListener()
             startDownloadForEachRequest(requestDownloads)
 
@@ -180,29 +163,14 @@ class FilesDownloadWorker(
             if (putResult != null) {
                 val downloadKey = putResult.first
                 requestedDownloads.add(downloadKey)
-                sendBroadcastNewDownload(operation, putResult.second)
+                localBroadcastManager.sendBroadcast(intents.newDownloadIntent(operation, putResult.second))
             }
-
         } catch (e: IllegalArgumentException) {
             Log_OC.e(TAG, "Not enough information provided in intent: " + e.message)
         }
 
         return requestedDownloads
     }
-    
-    private fun initNotificationBuilder() {
-        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils)
-            .setContentTitle(context.resources.getString(R.string.app_name))
-            .setContentText(context.resources.getString(R.string.foreground_service_download))
-            .setSmallIcon(R.drawable.notification_icon)
-            .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
-        }
-
-        notification = notificationBuilder.build()
-    }
 
     private fun addAccountUpdateListener() {
         val am = AccountManager.get(context)
@@ -221,6 +189,7 @@ class FilesDownloadWorker(
         startedDownload = false
     }
 
+    @Suppress("TooGenericExceptionCaught")
     private fun downloadFile(downloadKey: String) {
         startedDownload = true
         currentDownload = pendingDownloads.get(downloadKey)
@@ -244,7 +213,9 @@ class FilesDownloadWorker(
 
             downloadResult = currentDownload?.execute(downloadClient)
             if (downloadResult?.isSuccess == true && currentDownload?.downloadType === DownloadType.DOWNLOAD) {
-                saveDownloadedFile()
+                getCurrentFile()?.let {
+                    helper.saveFile(it, currentDownload, storageManager)
+                }
             }
         } catch (e: Exception) {
             Log_OC.e(TAG, "Error downloading", e)
@@ -261,14 +232,9 @@ class FilesDownloadWorker(
     private fun notifyDownloadStart(download: DownloadFileOperation) {
         lastPercent = 0
 
-        configureNotificationBuilderForDownloadStart(download)
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
-        }
-
-        showDetailsIntent(download)
-        notifyDownloadInProgressNotification()
+        notificationManager.notifyForStart(download)
+        notificationManager.setContentIntent(intents.detailsIntent(download), PendingIntent.FLAG_IMMUTABLE)
+        notificationManager.showDownloadInProgressNotification()
     }
 
     private fun getOCAccountForDownload(): OwnCloudAccount {
@@ -281,38 +247,6 @@ class FilesDownloadWorker(
         return currentDownloadUser.get().toOwnCloudAccount()
     }
 
-    private fun saveDownloadedFile() {
-        val file = getCurrentFile() ?: return
-
-        val syncDate = System.currentTimeMillis()
-
-        file.apply {
-            lastSyncDateForProperties = syncDate
-            lastSyncDateForData = syncDate
-            isUpdateThumbnailNeeded = true
-            modificationTimestamp = currentDownload?.modificationTimestamp ?: 0L
-            modificationTimestampAtLastSyncForData = currentDownload?.modificationTimestamp ?: 0L
-            etag = currentDownload?.etag
-            mimeType = currentDownload?.mimeType
-            storagePath = currentDownload?.savePath
-
-            val savePathFile = currentDownload?.savePath?.let { File(it) }
-            savePathFile?.let {
-                fileLength = savePathFile.length()
-            }
-
-            remoteId = currentDownload?.file?.remoteId
-        }
-
-        storageManager?.saveFile(file)
-
-        if (MimeTypeUtil.isMedia(currentDownload?.mimeType)) {
-            FileDataStorageManager.triggerMediaScan(file.storagePath, file)
-        }
-
-        storageManager?.saveConflict(file, null)
-    }
-
     private fun getCurrentFile(): OCFile? {
         var file: OCFile? = currentDownload?.file?.fileId?.let { storageManager?.getFileById(it) }
 
@@ -330,14 +264,20 @@ class FilesDownloadWorker(
 
     private fun cleanupDownloadProcess(result: RemoteOperationResult<*>?) {
         val removeResult = pendingDownloads.removePayload(
-            currentDownload?.user?.accountName, currentDownload?.remotePath
+            currentDownload?.user?.accountName,
+            currentDownload?.remotePath
         )
 
         val downloadResult = result ?: RemoteOperationResult<Any?>(RuntimeException("Error downloading…"))
 
         currentDownload?.run {
             notifyDownloadResult(this, downloadResult)
-            sendBroadcastDownloadFinished(this, downloadResult, removeResult.second)
+            val downloadFinishedIntent = intents.downloadFinishedIntent(
+                this,
+                downloadResult,
+                removeResult.second
+            )
+            localBroadcastManager.sendBroadcast(downloadFinishedIntent)
         }
     }
 
@@ -355,72 +295,15 @@ class FilesDownloadWorker(
         }
 
         val needsToUpdateCredentials = (ResultCode.UNAUTHORIZED == downloadResult.code)
-        configureNotificationBuilderForDownloadResult(downloadResult, needsToUpdateCredentials)
+        notificationManager.prepareForResult(downloadResult, needsToUpdateCredentials)
 
         if (needsToUpdateCredentials) {
-            configureUpdateCredentialsNotification(download.user)
+            notificationManager.setCredentialContentIntent(download.user)
         } else {
-            showDetailsIntent(null)
-        }
-
-        notifyNotificationBuilderForDownloadResult(downloadResult, download)
-    }
-
-    private fun configureNotificationBuilderForDownloadResult(
-        downloadResult: RemoteOperationResult<*>,
-        needsToUpdateCredentials: Boolean
-    ) {
-        var tickerId =
-            if (downloadResult.isSuccess) R.string.downloader_download_succeeded_ticker else R.string.downloader_download_failed_ticker
-        tickerId = if (needsToUpdateCredentials) R.string.downloader_download_failed_credentials_error else tickerId
-
-        notificationBuilder
-            .setTicker(context.getString(tickerId))
-            .setContentTitle(context.getString(tickerId))
-            .setAutoCancel(true)
-            .setOngoing(false)
-            .setProgress(0, 0, false)
-    }
-
-    private fun notifyNotificationBuilderForDownloadResult(downloadResult: RemoteOperationResult<*>, download: DownloadFileOperation) {
-        val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
-            downloadResult,
-            download,
-            context.resources
-        )
-
-        notificationBuilder.setContentText(errorMessage)
-
-        notificationManager.notify(SecureRandom().nextInt(), notificationBuilder.build())
-
-        if (downloadResult.isSuccess) {
-            NotificationUtils.cancelWithDelay(
-                notificationManager,
-                R.string.downloader_download_succeeded_ticker,
-                2000
-            )
-        }
-    }
-
-    private fun sendBroadcastDownloadFinished(
-        download: DownloadFileOperation,
-        downloadResult: RemoteOperationResult<*>,
-        unlinkedFromRemotePath: String?
-    ) {
-        val intent = Intent(getDownloadFinishMessage()).apply {
-            putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess)
-            putExtra(ACCOUNT_NAME, download.user.accountName)
-            putExtra(EXTRA_REMOTE_PATH, download.remotePath)
-            putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.behaviour)
-            putExtra(SendShareDialog.ACTIVITY_NAME, download.activityName)
-            putExtra(SendShareDialog.PACKAGE_NAME, download.packageName)
-            if (unlinkedFromRemotePath != null) {
-                putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
-            }
-            setPackage(context.packageName)
+            notificationManager.setContentIntent(intents.detailsIntent(null), PendingIntent.FLAG_IMMUTABLE)
         }
 
-        localBroadcastManager.sendBroadcast(intent)
+        notificationManager.notifyForResult(downloadResult, download)
     }
 
     private fun dismissDownloadInProgressNotification() {
@@ -430,82 +313,7 @@ class FilesDownloadWorker(
             }
         }
 
-        notificationManager.cancel(R.string.downloader_download_in_progress_ticker)
-    }
-
-    private fun configureUpdateCredentialsNotification(user: User) {
-        val intent = Intent(context, AuthenticatorActivity::class.java).apply {
-            putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount())
-            putExtra(
-                AuthenticatorActivity.EXTRA_ACTION,
-                AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
-            )
-            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-            addFlags(Intent.FLAG_FROM_BACKGROUND)
-        }
-
-        notificationBuilder.setContentIntent(
-            PendingIntent.getActivity(
-                context, System.currentTimeMillis().toInt(),
-                intent,
-                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
-            )
-        )
-    }
-
-    private fun configureNotificationBuilderForDownloadStart(download: DownloadFileOperation) {
-        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils)
-            .setSmallIcon(R.drawable.notification_icon)
-            .setTicker(context.getString(R.string.downloader_download_in_progress_ticker))
-            .setContentTitle(context.getString(R.string.downloader_download_in_progress_ticker))
-            .setOngoing(true)
-            .setProgress(100, 0, download.size < 0)
-            .setContentText(
-                String.format(
-                    context.getString(R.string.downloader_download_in_progress_content), 0,
-                    File(download.savePath).name
-                )
-            )
-    }
-
-    private fun showDetailsIntent(operation: DownloadFileOperation?) {
-        val intent: Intent = if (operation != null) {
-            if (PreviewImageFragment.canBePreviewed(operation.file)) {
-                Intent(context, PreviewImageActivity::class.java)
-            } else {
-                Intent(context, FileDisplayActivity::class.java)
-            }.apply {
-                putExtra(FileActivity.EXTRA_FILE, operation.file)
-                putExtra(FileActivity.EXTRA_USER, operation.user)
-                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
-            }
-        } else {
-            Intent()
-        }
-
-        notificationBuilder.setContentIntent(
-            PendingIntent.getActivity(
-                context,
-                System.currentTimeMillis().toInt(),
-                intent,
-                PendingIntent.FLAG_IMMUTABLE
-            )
-        )
-    }
-
-    private fun sendBroadcastNewDownload(
-        download: DownloadFileOperation,
-        linkedToRemotePath: String
-    ) {
-        val intent = Intent(getDownloadAddedMessage()).apply {
-            putExtra(ACCOUNT_NAME, download.user.accountName)
-            putExtra(EXTRA_REMOTE_PATH, download.remotePath)
-            putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath)
-            setPackage(context.packageName)
-        }
-
-        localBroadcastManager.sendBroadcast(intent)
+        notificationManager.dismissDownloadInProgressNotification()
     }
 
     override fun onAccountsUpdated(accounts: Array<out Account>?) {
@@ -514,6 +322,7 @@ class FilesDownloadWorker(
         }
     }
 
+    @Suppress("MagicNumber")
     override fun onTransferProgress(
         progressRate: Long,
         totalTransferredSoFar: Long,
@@ -523,25 +332,13 @@ class FilesDownloadWorker(
         val percent: Int = (100.0 * totalTransferredSoFar.toDouble() / totalToTransfer.toDouble()).toInt()
 
         if (percent != lastPercent) {
-            notificationBuilder.setProgress(100, percent, totalToTransfer < 0)
-            val fileName: String = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1)
-            val text =
-                String.format(context.getString(R.string.downloader_download_in_progress_content), percent, fileName)
-            notificationBuilder.setContentText(text)
-
-            notifyDownloadInProgressNotification()
+            notificationManager.updateDownloadProgressNotification(filePath, percent, totalToTransfer)
+            notificationManager.showDownloadInProgressNotification()
         }
 
         lastPercent = percent
     }
 
-    private fun notifyDownloadInProgressNotification() {
-        notificationManager.notify(
-            R.string.downloader_download_in_progress_ticker,
-            notificationBuilder.build()
-        )
-    }
-
     inner class FileDownloaderBinder : Binder(), OnDatatransferProgressListener {
         private val boundListeners: MutableMap<Long, OnDatatransferProgressListener> = HashMap()
 
@@ -601,8 +398,10 @@ class FilesDownloadWorker(
         ) {
             val listener = boundListeners[currentDownload?.file?.fileId]
             listener?.onTransferProgress(
-                progressRate, totalTransferredSoFar,
-                totalToTransfer, fileName
+                progressRate,
+                totalTransferredSoFar,
+                totalToTransfer,
+                fileName
             )
         }
     }

+ 1 - 0
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -145,6 +145,7 @@ interface BackgroundJobManager {
     fun getFileUploads(user: User): LiveData<List<JobInfo>>
     fun cancelFilesUploadJob(user: User)
 
+    @Suppress("LongParameterList")
     fun startFilesDownloadJob(
         user: User,
         ocFile: OCFile,

+ 1 - 1
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -527,7 +527,7 @@ internal class BackgroundJobManagerImpl(
             FilesDownloadWorker.DOWNLOAD_TYPE to downloadType.toString(),
             FilesDownloadWorker.ACTIVITY_NAME to activityName,
             FilesDownloadWorker.PACKAGE_NAME to packageName,
-            FilesDownloadWorker.CONFLICT_UPLOAD_ID to conflictUploadId,
+            FilesDownloadWorker.CONFLICT_UPLOAD_ID to conflictUploadId
         )
 
         val request = oneTimeRequestBuilder(FilesDownloadWorker::class, JOB_FILES_DOWNLOAD, user)

+ 175 - 0
app/src/main/java/com/nextcloud/client/notifications/download/DownloadNotificationManager.kt

@@ -0,0 +1,175 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.notifications.download
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import com.nextcloud.client.account.User
+import com.owncloud.android.R
+import com.owncloud.android.authentication.AuthenticatorActivity
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.resources.files.FileUtils
+import com.owncloud.android.operations.DownloadFileOperation
+import com.owncloud.android.ui.notifications.NotificationUtils
+import com.owncloud.android.utils.ErrorMessageAdapter
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import java.io.File
+import java.security.SecureRandom
+
+class DownloadNotificationManager(private val context: Context, private val viewThemeUtils: ViewThemeUtils) {
+
+    private var notification: Notification? = null
+    private lateinit var notificationBuilder: NotificationCompat.Builder
+    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+    fun init() {
+        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils)
+            .setContentTitle(context.resources.getString(R.string.app_name))
+            .setContentText(context.resources.getString(R.string.foreground_service_download))
+            .setSmallIcon(R.drawable.notification_icon)
+            .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
+        }
+
+        notification = notificationBuilder.build()
+    }
+
+    @Suppress("MagicNumber")
+    fun notifyForStart(operation: DownloadFileOperation) {
+        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils)
+            .setSmallIcon(R.drawable.notification_icon)
+            .setTicker(context.getString(R.string.downloader_download_in_progress_ticker))
+            .setContentTitle(context.getString(R.string.downloader_download_in_progress_ticker))
+            .setOngoing(true)
+            .setProgress(100, 0, operation.size < 0)
+            .setContentText(
+                String.format(
+                    context.getString(R.string.downloader_download_in_progress_content), 0,
+                    File(operation.savePath).name
+                )
+            )
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
+        }
+    }
+
+    @Suppress("MagicNumber")
+    fun notifyForResult(result: RemoteOperationResult<*>, download: DownloadFileOperation) {
+        val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
+            result,
+            download,
+            context.resources
+        )
+
+        notificationBuilder.setContentText(errorMessage)
+
+        notificationManager.notify(SecureRandom().nextInt(), notificationBuilder.build())
+
+        if (result.isSuccess) {
+            NotificationUtils.cancelWithDelay(
+                notificationManager,
+                R.string.downloader_download_succeeded_ticker,
+                2000
+            )
+        }
+    }
+
+    fun prepareForResult(
+        downloadResult: RemoteOperationResult<*>,
+        needsToUpdateCredentials: Boolean
+    ) {
+        var tickerId =
+            if (downloadResult.isSuccess) {
+                R.string.downloader_download_succeeded_ticker
+            } else {
+                R.string.downloader_download_failed_ticker
+            }
+
+        tickerId = if (needsToUpdateCredentials) {
+            R.string.downloader_download_failed_credentials_error
+        } else {
+            tickerId
+        }
+
+        notificationBuilder
+            .setTicker(context.getString(tickerId))
+            .setContentTitle(context.getString(tickerId))
+            .setAutoCancel(true)
+            .setOngoing(false)
+            .setProgress(0, 0, false)
+    }
+
+    @Suppress("MagicNumber")
+    fun updateDownloadProgressNotification(filePath: String, percent: Int, totalToTransfer: Long) {
+        notificationBuilder.setProgress(100, percent, totalToTransfer < 0)
+        val fileName: String = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1)
+        val text =
+            String.format(context.getString(R.string.downloader_download_in_progress_content), percent, fileName)
+        notificationBuilder.setContentText(text)
+    }
+
+    fun showDownloadInProgressNotification() {
+        notificationManager.notify(
+            R.string.downloader_download_in_progress_ticker,
+            notificationBuilder.build()
+        )
+    }
+
+    fun dismissDownloadInProgressNotification() {
+        notificationManager.cancel(R.string.downloader_download_in_progress_ticker)
+    }
+
+    fun setCredentialContentIntent(user: User) {
+        val intent = Intent(context, AuthenticatorActivity::class.java).apply {
+            putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount())
+            putExtra(
+                AuthenticatorActivity.EXTRA_ACTION,
+                AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
+            )
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+            addFlags(Intent.FLAG_FROM_BACKGROUND)
+        }
+
+        setContentIntent(intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)
+    }
+
+    fun setContentIntent(intent: Intent, flag: Int) {
+        notificationBuilder.setContentIntent(
+            PendingIntent.getActivity(
+                context,
+                System.currentTimeMillis().toInt(),
+                intent,
+                flag
+            )
+        )
+    }
+}