From d984c32f3f19685e826772f2087c9a6ab82ee49c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 20 Dec 2023 12:46:36 +0100 Subject: [PATCH] Cleanup code Signed-off-by: alperozturk --- .../files/downloader/FilesDownloadHelper.kt | 39 +++ .../files/downloader/FilesDownloadIntents.kt | 83 ++++++ .../files/downloader/FilesDownloadWorker.kt | 273 +++--------------- .../client/jobs/BackgroundJobFactory.kt | 2 +- .../client/jobs/BackgroundJobManager.kt | 1 + .../client/jobs/BackgroundJobManagerImpl.kt | 2 +- .../download/DownloadNotificationManager.kt | 175 +++++++++++ 7 files changed, 336 insertions(+), 239 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadIntents.kt create mode 100644 app/src/main/java/com/nextcloud/client/notifications/download/DownloadNotificationManager.kt diff --git a/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadHelper.kt b/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadHelper.kt index 94803b9b297c..328d4b4b164d 100644 --- a/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadHelper.kt +++ b/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, diff --git a/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadIntents.kt b/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadIntents.kt new file mode 100644 index 000000000000..d8d6e67a2c8f --- /dev/null +++ b/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 . + */ + +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() + } + } +} diff --git a/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadWorker.kt b/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadWorker.kt index b9bd074c64ef..79bd327cc50a 100644 --- a/app/src/main/java/com/nextcloud/client/files/downloader/FilesDownloadWorker.kt +++ b/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() private var downloadBinder: IBinder = FileDownloaderBinder() private var currentUser = Optional.empty() + 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(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?) { @@ -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 = HashMap() @@ -601,8 +398,10 @@ class FilesDownloadWorker( ) { val listener = boundListeners[currentDownload?.file?.fileId] listener?.onTransferProgress( - progressRate, totalTransferredSoFar, - totalToTransfer, fileName + progressRate, + totalTransferredSoFar, + totalToTransfer, + fileName ) } } 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 8aa2fef499af..0d4a2d9bb874 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -52,7 +52,7 @@ import javax.inject.Provider * * This class is doing too many things and should be split up into smaller factories. */ -@Suppress("LongParameterList") // satisfied by DI +@Suppress("LongParameterList", "TooManyFunctions") // satisfied by DI class BackgroundJobFactory @Inject constructor( private val logger: Logger, private val preferences: AppPreferences, 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 7787ff8a8a26..8c9f0ddb7703 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt @@ -140,6 +140,7 @@ interface BackgroundJobManager { fun getFileUploads(user: User): LiveData> fun cancelFilesUploadJob(user: User) + @Suppress("LongParameterList") fun startFilesDownloadJob( user: User, ocFile: OCFile, 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 6540e83ea926..cb675449894e 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -480,7 +480,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) diff --git a/app/src/main/java/com/nextcloud/client/notifications/download/DownloadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/notifications/download/DownloadNotificationManager.kt new file mode 100644 index 000000000000..0bd056e19cbb --- /dev/null +++ b/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 . + */ + +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 + ) + ) + } +}