diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index 2b247b968..ad85e1ef8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -221,7 +221,9 @@ class EntryActivity : LockingActivity() { registerProgressTask() onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener { override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) { - entryContentsView?.putAttachment(entryAttachmentState) + if (entryAttachmentState.streamDirection != StreamDirection.UPLOAD) { + entryContentsView?.putAttachment(entryAttachmentState) + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 1f9fe10fa..135167d7e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -372,6 +372,9 @@ class EntryEditActivity : LockingActivity(), } } } + AttachmentState.CANCELED -> { + entryEditFragment?.removeAttachment(entryAttachmentState) + } AttachmentState.ERROR -> { entryEditFragment?.removeAttachment(entryAttachmentState) coordinatorLayout?.let { @@ -525,6 +528,7 @@ class EntryEditActivity : LockingActivity(), * Saves the new entry or update an existing entry in the database */ private fun saveEntry() { + mAttachmentFileBinderManager?.stopUploadAllAttachments() // Get the temp entry entryEditFragment?.getEntryInfo()?.let { newEntryInfo -> @@ -542,6 +546,7 @@ class EntryEditActivity : LockingActivity(), when (attachmentState.downloadState) { AttachmentState.START, AttachmentState.IN_PROGRESS, + AttachmentState.CANCELED, AttachmentState.ERROR -> { // Remove attachment not finished from info newEntryInfo.attachments = newEntryInfo.attachments.toMutableList().apply { @@ -785,6 +790,7 @@ class EntryEditActivity : LockingActivity(), .setMessage(R.string.discard_changes) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.discard) { _, _ -> + mAttachmentFileBinderManager?.stopUploadAllAttachments() backPressedAlreadyApproved = true approved.invoke() }.create().show() diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt index 1d167b8f9..904aac8b6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt @@ -142,6 +142,7 @@ class EntryAttachmentsItemsAdapter(context: Context) } AttachmentState.NULL, AttachmentState.ERROR, + AttachmentState.CANCELED, AttachmentState.COMPLETE -> { holder.binaryFileProgressContainer.visibility = View.GONE holder.binaryFileProgress.visibility = View.GONE @@ -159,8 +160,13 @@ class EntryAttachmentsItemsAdapter(context: Context) holder.binaryFileDeleteButton.visibility = View.GONE holder.binaryFileProgress.apply { visibility = when (entryAttachmentState.downloadState) { - AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE - AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE + AttachmentState.NULL, + AttachmentState.COMPLETE, + AttachmentState.CANCELED, + AttachmentState.ERROR -> View.GONE + + AttachmentState.START, + AttachmentState.IN_PROGRESS -> View.VISIBLE } progress = entryAttachmentState.downloadProgression } diff --git a/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt b/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt index b1ad9df53..26b9ed5e4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt @@ -76,5 +76,5 @@ data class EntryAttachmentState(var attachment: Attachment, } enum class AttachmentState { - NULL, START, IN_PROGRESS, COMPLETE, ERROR + NULL, START, IN_PROGRESS, COMPLETE, CANCELED, ERROR } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt index 1b19cada1..adfd276ca 100644 --- a/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt @@ -35,10 +35,8 @@ import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.stream.readAllBytes -import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.utils.UriUtil import kotlinx.coroutines.* -import java.io.BufferedInputStream import java.util.* import java.util.concurrent.CopyOnWriteArrayList @@ -101,12 +99,15 @@ class AttachmentFileNotificationService: LockNotificationService() { when(intent?.action) { ACTION_ATTACHMENT_FILE_START_UPLOAD -> { - actionUploadOrDownload(downloadFileUri, + actionStartUploadOrDownload(downloadFileUri, intent, StreamDirection.UPLOAD) } + ACTION_ATTACHMENT_FILE_STOP_UPLOAD -> { + actionStopUpload() + } ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> { - actionUploadOrDownload(downloadFileUri, + actionStartUploadOrDownload(downloadFileUri, intent, StreamDirection.DOWNLOAD) } @@ -216,15 +217,22 @@ class AttachmentFileNotificationService: LockNotificationService() { setDeleteIntent(pendingDeleteIntent) setOngoing(false) } + AttachmentState.CANCELED -> { + setContentText(getString(R.string.download_canceled)) + setDeleteIntent(pendingDeleteIntent) + setOngoing(false) + } AttachmentState.ERROR -> { setContentText(getString(R.string.error_file_not_create)) + setDeleteIntent(pendingDeleteIntent) setOngoing(false) } } } when (attachmentNotification.entryAttachmentState.downloadState) { - AttachmentState.ERROR, - AttachmentState.COMPLETE -> { + AttachmentState.COMPLETE, + AttachmentState.CANCELED, + AttachmentState.ERROR -> { stopForeground(false) notificationManager?.notify(attachmentNotification.notificationId, builder.build()) } else -> { @@ -235,6 +243,7 @@ class AttachmentFileNotificationService: LockNotificationService() { override fun onDestroy() { attachmentNotificationList.forEach { attachmentNotification -> + attachmentNotification.attachmentFileAction?.cancel() attachmentNotification.attachmentFileAction?.listener = null notificationManager?.cancel(attachmentNotification.notificationId) } @@ -263,10 +272,10 @@ class AttachmentFileNotificationService: LockNotificationService() { } } - private fun actionUploadOrDownload(downloadFileUri: Uri?, - intent: Intent, - streamDirection: StreamDirection) { - if (downloadFileUri != null + private fun actionStartUploadOrDownload(fileUri: Uri?, + intent: Intent, + streamDirection: StreamDirection) { + if (fileUri != null && intent.hasExtra(ATTACHMENT_KEY)) { try { intent.getParcelableExtra(ATTACHMENT_KEY)?.let { entryAttachment -> @@ -274,7 +283,7 @@ class AttachmentFileNotificationService: LockNotificationService() { val nextNotificationId = (attachmentNotificationList.maxByOrNull { it.notificationId } ?.notificationId ?: notificationId) + 1 val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection) - val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState) + val attachmentNotification = AttachmentNotification(fileUri, nextNotificationId, entryAttachmentState) // Add action to the list on start attachmentNotificationList.add(attachmentNotification) @@ -287,11 +296,24 @@ class AttachmentFileNotificationService: LockNotificationService() { } } } catch (e: Exception) { - Log.e(TAG, "Unable to upload/download $downloadFileUri", e) + Log.e(TAG, "Unable to upload/download $fileUri", e) } } } + private fun actionStopUpload() { + try { + // Stop each upload + attachmentNotificationList.filter { + it.entryAttachmentState.streamDirection == StreamDirection.UPLOAD + }.forEach { + it.attachmentFileAction?.cancel() + } + } catch (e: Exception) { + Log.e(TAG, "Unable to stop upload", e) + } + } + private class AttachmentFileAction( private val attachmentNotification: AttachmentNotification, private val contentResolver: ContentResolver) { @@ -308,8 +330,6 @@ class AttachmentFileNotificationService: LockNotificationService() { // on pre execute CoroutineScope(Dispatchers.Main).launch { - TimeoutHelper.temporarilyDisableTimeout() - attachmentNotification.attachmentFileAction = this@AttachmentFileAction attachmentNotification.entryAttachmentState.apply { downloadState = AttachmentState.START @@ -320,53 +340,62 @@ class AttachmentFileNotificationService: LockNotificationService() { withContext(Dispatchers.IO) { // on Progress with thread - val asyncResult: Deferred = async { - var progressResult = true - try { - attachmentNotification.entryAttachmentState.apply { - downloadState = AttachmentState.IN_PROGRESS + val asyncAction = launch { + attachmentNotification.entryAttachmentState.apply { + try { + downloadState = AttachmentState.IN_PROGRESS - when (streamDirection) { - StreamDirection.UPLOAD -> { - uploadToDatabase( - attachmentNotification.uri, - attachment.binaryAttachment, - contentResolver, 1024) { percent -> - publishProgress(percent) + when (streamDirection) { + StreamDirection.UPLOAD -> { + uploadToDatabase( + attachmentNotification.uri, + attachment.binaryAttachment, + contentResolver, 1024, + { // Cancellation + downloadState == AttachmentState.CANCELED + } + ) { percent -> + publishProgress(percent) + } + } + StreamDirection.DOWNLOAD -> { + downloadFromDatabase( + attachmentNotification.uri, + attachment.binaryAttachment, + contentResolver, 1024) { percent -> + publishProgress(percent) + } } } - StreamDirection.DOWNLOAD -> { - downloadFromDatabase( - attachmentNotification.uri, - attachment.binaryAttachment, - contentResolver, 1024) { percent -> - publishProgress(percent) - } - } - } + } catch (e: Exception) { + e.printStackTrace() + downloadState = AttachmentState.ERROR } - } catch (e: Exception) { - e.printStackTrace() - progressResult = false } - progressResult + attachmentNotification.entryAttachmentState.downloadState } // on post execute withContext(Dispatchers.Main) { - val result = asyncResult.await() - attachmentNotification.attachmentFileAction = null + asyncAction.join() attachmentNotification.entryAttachmentState.apply { - downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR - downloadProgression = 100 + if (downloadState != AttachmentState.CANCELED + && downloadState != AttachmentState.ERROR) { + downloadState = AttachmentState.COMPLETE + downloadProgression = 100 + } } + attachmentNotification.attachmentFileAction = null listener?.onUpdate(attachmentNotification) - TimeoutHelper.releaseTemporarilyDisableTimeout() } } } + fun cancel() { + attachmentNotification.entryAttachmentState.downloadState = AttachmentState.CANCELED + } + fun downloadFromDatabase(attachmentToUploadUri: Uri, binaryAttachment: BinaryAttachment, contentResolver: ContentResolver, @@ -396,13 +425,14 @@ class AttachmentFileNotificationService: LockNotificationService() { binaryAttachment: BinaryAttachment, contentResolver: ContentResolver, bufferSize: Int = DEFAULT_BUFFER_SIZE, + canceled: ()-> Boolean = { false }, update: ((percent: Int)->Unit)? = null) { var dataUploaded = 0L val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0 UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream -> Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> binaryAttachment.getGzipOutputDataStream(binaryCipherKey).use { outputStream -> - inputStream.readAllBytes(bufferSize) { buffer -> + inputStream.readAllBytes(bufferSize, canceled) { buffer -> outputStream.write(buffer) dataUploaded += buffer.size try { @@ -422,7 +452,9 @@ class AttachmentFileNotificationService: LockNotificationService() { val currentTime = System.currentTimeMillis() if (previousSaveTime + updateMinFrequency < currentTime) { attachmentNotification.entryAttachmentState.apply { - downloadState = AttachmentState.IN_PROGRESS + if (downloadState != AttachmentState.CANCELED) { + downloadState = AttachmentState.IN_PROGRESS + } downloadProgression = percent } CoroutineScope(Dispatchers.Main).launch { @@ -444,6 +476,7 @@ class AttachmentFileNotificationService: LockNotificationService() { private const val CHANNEL_ATTACHMENT_ID = "com.kunzisoft.keepass.notification.channel.attachment" const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD" + const val ACTION_ATTACHMENT_FILE_STOP_UPLOAD = "ACTION_ATTACHMENT_FILE_STOP_UPLOAD" const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD" const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE" diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/StreamBytesUtils.kt b/app/src/main/java/com/kunzisoft/keepass/stream/StreamBytesUtils.kt index 4b88e00ee..e8ede8248 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/StreamBytesUtils.kt +++ b/app/src/main/java/com/kunzisoft/keepass/stream/StreamBytesUtils.kt @@ -31,10 +31,11 @@ import java.util.* */ @Throws(IOException::class) fun InputStream.readAllBytes(bufferSize: Int = DEFAULT_BUFFER_SIZE, + cancelCondition: ()-> Boolean = { false }, readBytes: (bytesRead: ByteArray) -> Unit) { val buffer = ByteArray(bufferSize) var read = 0 - while (read != -1) { + while (read != -1 && !cancelCondition()) { read = this.read(buffer, 0, buffer.size) if (read != -1) { val optimizedBuffer: ByteArray = if (buffer.size == read) { diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt index 084b12bd2..933a47536 100644 --- a/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt @@ -34,6 +34,7 @@ import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.services.AttachmentFileNotificationService import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD +import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_STOP_UPLOAD import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE class AttachmentFileBinderManager(private val activity: FragmentActivity) { @@ -120,6 +121,10 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) { }, ACTION_ATTACHMENT_FILE_START_UPLOAD) } + fun stopUploadAllAttachments() { + start(null, ACTION_ATTACHMENT_FILE_STOP_UPLOAD) + } + fun startDownloadAttachment(downloadFileUri: Uri, attachment: Attachment) { start(Bundle().apply { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3aaa72ef0..05af573b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -514,6 +514,7 @@ In progress: %1$d%% Finalizing… Complete! + Canceled! Rijndael (AES) Twofish ChaCha20