diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt index 4374926d7a..77226df71d 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/base/BaseTest.kt @@ -42,6 +42,7 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until import com.flowcrypt.email.BuildConfig +import com.flowcrypt.email.Constants import com.flowcrypt.email.R import com.flowcrypt.email.api.email.MsgsCacheManager import com.flowcrypt.email.api.email.model.AttachmentInfo @@ -387,5 +388,8 @@ abstract class BaseTest : BaseActivityTestImplementation { companion object{ const val NOTIFICATION_RESOURCES_NAME = "com.android.systemui:id/expandableNotificationRow" + + val SHARED_FOLDER = InstrumentationRegistry.getInstrumentation().targetContext + .getExternalFilesDir(Constants.EXTERNAL_FILES_PATH_SHARED) } } diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenFlowTest.kt index a9da21ed06..a40f54e5e3 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenFlowTest.kt @@ -97,7 +97,7 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class ComposeScreenFlowTest : BaseComposeScreenTest() { private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule() - private val temporaryFolderRule = TemporaryFolder() + private val temporaryFolderRule = TemporaryFolder.builder().parentFolder(SHARED_FOLDER).build() @get:Rule var ruleChain: TestRule = RuleChain @@ -907,7 +907,7 @@ class ComposeScreenFlowTest : BaseComposeScreenTest() { @get:ClassRule @JvmStatic - val temporaryFolderRule = TemporaryFolder() + val temporaryFolderRule = TemporaryFolder.builder().parentFolder(SHARED_FOLDER).build() @get:ClassRule @JvmStatic diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenPasswordProtectedFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenPasswordProtectedFlowTest.kt index 18929e1db7..7ea7fd7cdd 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenPasswordProtectedFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenPasswordProtectedFlowTest.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.ui @@ -48,7 +48,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ComposeScreenPasswordProtectedFlowTest : BaseComposeScreenTest() { private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule() - private val temporaryFolderRule = TemporaryFolder() + private val temporaryFolderRule = TemporaryFolder.builder().parentFolder(SHARED_FOLDER).build() override val addAccountToDatabaseRule: AddAccountToDatabaseRule get() = AddAccountToDatabaseRule( diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsFlowTest.kt index 6a3da83272..7ec2d0470f 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsFlowTest.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.ui @@ -8,7 +8,6 @@ package com.flowcrypt.email.ui import android.app.Activity import android.app.Instrumentation import android.content.Intent -import android.os.Environment import androidx.core.content.FileProvider import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView @@ -145,7 +144,7 @@ class PublicKeyDetailsFlowTest : BaseTest() { val fileName = "0x" + keyDetails.fingerprint + "-" + sanitizedEmail + "-publickey" + ".asc" val file = - File(getTargetContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName) + File(getTargetContext().getExternalFilesDir(Constants.EXTERNAL_FILES_PATH_SHARED), fileName) if (file.exists()) { file.delete() diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsHideArmorMetaFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsHideArmorMetaFlowTest.kt index b02c653344..503cc8d108 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsHideArmorMetaFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/PublicKeyDetailsHideArmorMetaFlowTest.kt @@ -8,7 +8,6 @@ package com.flowcrypt.email.ui import android.app.Activity import android.app.Instrumentation import android.content.Intent -import android.os.Environment import androidx.core.content.FileProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -122,7 +121,7 @@ class PublicKeyDetailsHideArmorMetaFlowTest : BaseTest() { val fileName = "0x" + keyDetails.fingerprint + "-" + sanitizedEmail + "-publickey" + ".asc" val file = - File(getTargetContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName) + File(getTargetContext().getExternalFilesDir(Constants.EXTERNAL_FILES_PATH_SHARED), fileName) if (file.exists()) { file.delete() diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/fragment/isolation/incontainer/PrivateKeyDetailsFragmentInIsolationTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/fragment/isolation/incontainer/PrivateKeyDetailsFragmentInIsolationTest.kt index 3172b16d84..fcda9a11c7 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/fragment/isolation/incontainer/PrivateKeyDetailsFragmentInIsolationTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/fragment/isolation/incontainer/PrivateKeyDetailsFragmentInIsolationTest.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.ui.fragment.isolation.incontainer @@ -8,7 +8,6 @@ package com.flowcrypt.email.ui.fragment.isolation.incontainer import android.app.Activity import android.app.Instrumentation import android.content.Intent -import android.os.Environment import androidx.core.content.FileProvider import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.onView @@ -164,7 +163,7 @@ class PrivateKeyDetailsFragmentInIsolationTest : BaseTest() { val details = addPrivateKeyToDatabaseRule.pgpKeyRingDetails val file = File( - getTargetContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), + getTargetContext().getExternalFilesDir(Constants.EXTERNAL_FILES_PATH_SHARED), "0x" + details.fingerprint + ".asc" ) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/TestGeneralUtil.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/TestGeneralUtil.kt index d0823a80f1..0fd7dd2b7d 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/TestGeneralUtil.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/util/TestGeneralUtil.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: ivan + * Contributors: denbond7 */ package com.flowcrypt.email.util @@ -9,7 +9,6 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle -import android.os.Environment import androidx.core.content.FileProvider import androidx.navigation.NavDeepLinkBuilder import androidx.test.core.app.ApplicationProvider @@ -97,7 +96,7 @@ object TestGeneralUtil { fun createFileWithContent(fileName: String, byteArray: ByteArray): File { val file = File( InstrumentationRegistry.getInstrumentation().targetContext - .getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName + .getExternalFilesDir(Constants.EXTERNAL_FILES_PATH_SHARED), fileName ) try { FileOutputStream(file).use { outputStream -> outputStream.write(byteArray) } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/Constants.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/Constants.kt index 784d9aefa3..0a6e0136c2 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/Constants.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/Constants.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email @@ -74,6 +74,7 @@ class Constants { const val FORWARDED_ATTACHMENTS_CACHE_DIR = "forwarded" const val ATTACHMENTS_CACHE_DIR = "attachments" const val DRAFT_CACHE_DIR = "draft" + const val EXTERNAL_FILES_PATH_SHARED = "shared" /** * The password quality types. diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ComposeMsgViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ComposeMsgViewModel.kt index b52387be97..9b9803d38e 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ComposeMsgViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ComposeMsgViewModel.kt @@ -7,7 +7,10 @@ package com.flowcrypt.email.jetpack.viewmodel import android.app.Application import android.content.ContentResolver +import android.content.Context +import android.net.Uri import androidx.lifecycle.viewModelScope +import com.flowcrypt.email.Constants import com.flowcrypt.email.api.email.model.AttachmentInfo import com.flowcrypt.email.api.email.model.OutgoingMessageInfo import com.flowcrypt.email.api.retrofit.response.base.Result @@ -24,6 +27,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.io.File import java.io.InvalidObjectException /** @@ -99,9 +103,19 @@ class ComposeMsgViewModel(isCandidateToEncrypt: Boolean, application: Applicatio val allRecipients: Map get() = recipientsTo + recipientsCc + recipientsBcc - val hasAttachmentsWithExternalStorageUri: Boolean - get() = attachmentsStateFlow.value.any { - ContentResolver.SCHEME_FILE.equals(it.uri?.scheme, ignoreCase = true) + val hasAttachmentsWittForeignExternalStorageUri: Boolean + get() { + val context: Context = getApplication() + val draftsCacheDirUri = Uri.fromFile(File(context.cacheDir, Constants.DRAFT_CACHE_DIR)) + + return attachmentsStateFlow.value.any { + val parentUri = it.uri?.buildUpon()?.path( + it.uri.path?.dropLast(it.uri.lastPathSegment?.length?.plus(1) ?: 0) + )?.build() + + ContentResolver.SCHEME_FILE.equals(it.uri?.scheme, ignoreCase = true) + && parentUri != draftsCacheDirUri + } } private val outgoingMessageInfoMutableStateFlow: MutableStateFlow = diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/DownloadForwardedAttachmentsWorker.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/DownloadForwardedAttachmentsWorker.kt index 16b55fc88f..6f10048355 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/DownloadForwardedAttachmentsWorker.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/DownloadForwardedAttachmentsWorker.kt @@ -7,7 +7,6 @@ package com.flowcrypt.email.jetpack.workmanager import android.content.Context import android.net.Uri -import androidx.core.content.FileProvider import androidx.core.net.toUri import androidx.work.CoroutineWorker import androidx.work.ExistingWorkPolicy @@ -27,7 +26,6 @@ import com.flowcrypt.email.util.FileAndDirectoryUtils import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.LogsUtil import com.flowcrypt.email.util.exception.ExceptionUtil -import com.google.android.gms.common.util.CollectionUtils import jakarta.mail.Folder import jakarta.mail.Store import kotlinx.coroutines.Dispatchers @@ -46,8 +44,10 @@ import java.util.UUID */ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParameters) : BaseWorker(context, params) { - private val attCacheDir = File(applicationContext.cacheDir, Constants.ATTACHMENTS_CACHE_DIR) - private val fwdAttsCacheDir = File(attCacheDir, Constants.FORWARDED_ATTACHMENTS_CACHE_DIR) + private val attachmentsCacheDir = + File(applicationContext.cacheDir, Constants.ATTACHMENTS_CACHE_DIR) + private val forwardedAttachmentsCacheDir = + File(attachmentsCacheDir, Constants.FORWARDED_ATTACHMENTS_CACHE_DIR) override suspend fun doWork(): Result = withContext(Dispatchers.IO) { @@ -57,31 +57,35 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } try { - if (!attCacheDir.exists()) { - if (!attCacheDir.mkdirs()) { - throw IllegalStateException("Create cache directory " + attCacheDir.name + " failed!") + if (!attachmentsCacheDir.exists()) { + if (!attachmentsCacheDir.mkdirs()) { + throw IllegalStateException( + "Creating cache directory ${attachmentsCacheDir.name} failed!" + ) } } - if (!fwdAttsCacheDir.exists()) { - if (!fwdAttsCacheDir.mkdirs()) { - throw IllegalStateException("Create cache directory " + fwdAttsCacheDir.name + " failed!") + if (!forwardedAttachmentsCacheDir.exists()) { + if (!forwardedAttachmentsCacheDir.mkdirs()) { + throw IllegalStateException( + "Creating cache directory ${forwardedAttachmentsCacheDir.name} failed!" + ) } } val account = roomDatabase.accountDao().getActiveAccountSuspend()?.withDecryptedInfo() ?: return@withContext Result.success() - val newMsgs = roomDatabase.msgDao().getOutboxMsgsByStatesSuspend( + val newForwardedMessages = roomDatabase.msgDao().getOutboxMsgsByStatesSuspend( account = account.email, msgStates = listOf(MessageState.NEW_FORWARDED.value) ) - if (!CollectionUtils.isEmpty(newMsgs)) { + if (newForwardedMessages.isNotEmpty()) { if (account.useAPI) { when (account.accountType) { AccountEntity.ACCOUNT_TYPE_GOOGLE -> { - downloadForwardedAtts(account) + downloadForwardedAttachments(account) } else -> throw IllegalStateException("Unsupported provider") @@ -92,7 +96,7 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet account, OpenStoreHelper.getAccountSess(applicationContext, account) ).use { store -> - downloadForwardedAtts(account, store) + downloadForwardedAttachments(account, store) } } } @@ -105,7 +109,7 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } } - private suspend fun downloadForwardedAtts(account: AccountEntity, store: Store? = null) = + private suspend fun downloadForwardedAttachments(account: AccountEntity, store: Store? = null) = withContext(Dispatchers.IO) { val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext) @@ -113,28 +117,40 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet val detailsList = roomDatabase.msgDao().getOutboxMsgsByStatesSuspend( account = account.email, msgStates = listOf(MessageState.NEW_FORWARDED.value) - ) + ).takeIf { it.isNotEmpty() } ?: break - if (CollectionUtils.isEmpty(detailsList)) { - break - } - - val msgEntity = detailsList[0] - val msgAttsDir = File(attCacheDir, msgEntity.attachmentsDirectory!!) try { - val atts = roomDatabase.attachmentDao().getAttachments( + val msgEntity = detailsList.first() + val tempDirectoryForForwardedAttachments = File( + forwardedAttachmentsCacheDir, requireNotNull(msgEntity.attachmentsDirectory) + ) + + val attachmentEntities = roomDatabase.attachmentDao().getAttachments( account = account.email, accountType = account.accountType, label = JavaEmailConstants.FOLDER_OUTBOX, uid = msgEntity.uid ).filter { it.isForwarded } - if (atts.isEmpty()) { + if (attachmentEntities.isEmpty()) { roomDatabase.msgDao().updateSuspend(msgEntity.copy(state = MessageState.QUEUED.value)) continue + } else { + if (!tempDirectoryForForwardedAttachments.exists()) { + if (!tempDirectoryForForwardedAttachments.mkdirs()) { + throw IllegalStateException( + "Creating cache directory ${tempDirectoryForForwardedAttachments.name} failed!" + ) + } + } } - val msgState = getNewMsgState(account, msgAttsDir, atts, store) + val msgState = getNewMsgState( + account = account, + parentDirectory = tempDirectoryForForwardedAttachments, + attachmentEntities = attachmentEntities, + store = store + ) val updateResult = roomDatabase.msgDao().updateSuspend( msgEntity.copy( @@ -173,13 +189,16 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } private suspend fun getNewMsgState( - account: AccountEntity, msgAttsDir: File, atts: List, store: Store? + account: AccountEntity, + parentDirectory: File, + attachmentEntities: List, + store: Store? ): MessageState = withContext(Dispatchers.IO) { return@withContext if (account.useAPI) { when (account.accountType) { AccountEntity.ACCOUNT_TYPE_GOOGLE -> { - val msg = atts.first().forwardedUid?.toHex() + val msg = attachmentEntities.first().forwardedUid?.toHex() ?.let { GmailApiHelper.loadMsgInfoSuspend( context = applicationContext, @@ -190,7 +209,7 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } ?: return@withContext MessageState.ERROR_ORIGINAL_MESSAGE_MISSING - loadAttachments(atts, msgAttsDir) { attachmentEntity -> + loadAttachments(attachmentEntities, parentDirectory) { attachmentEntity -> GmailApiHelper.getAttPartByPath(msg.payload, neededPath = attachmentEntity.path) ?.let { attPart -> GmailApiHelper.getAttInputStream( @@ -207,11 +226,12 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } } else { store?.let { - store.getFolder(atts.first().forwardedFolder).use { folder -> + store.getFolder(attachmentEntities.first().forwardedFolder).use { folder -> val imapFolder = (folder as IMAPFolder).apply { open(Folder.READ_ONLY) } - val fwdMsg = atts.first().forwardedUid?.let { uid -> imapFolder.getMessageByUID(uid) } + val fwdMsg = + attachmentEntities.first().forwardedUid?.let { uid -> imapFolder.getMessageByUID(uid) } ?: return@withContext MessageState.ERROR_ORIGINAL_MESSAGE_MISSING - loadAttachments(atts, msgAttsDir) { attachmentEntity -> + loadAttachments(attachmentEntities, parentDirectory) { attachmentEntity -> ImapProtocolUtil.getAttPartByPath( fwdMsg, neededPath = attachmentEntity.path @@ -223,36 +243,31 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } private suspend fun loadAttachments( - atts: List, msgAttsDir: File, - action: suspend (attachmentEntity: AttachmentEntity) - -> InputStream? + attachmentEntities: List, + parentDirectory: File, + action: suspend (attachmentEntity: AttachmentEntity) -> InputStream? ): MessageState = withContext(Dispatchers.IO) { var msgState = MessageState.QUEUED - for (attachmentEntity in atts) { + FileAndDirectoryUtils.cleanDir(parentDirectory) + for (attachmentEntity in attachmentEntities) { if (!attachmentEntity.isForwarded) { continue } val attName = attachmentEntity.name - val attFile = File(msgAttsDir, attName) + val attFile = File(parentDirectory, attName) val exists = attFile.exists() var uri: Uri? when { exists -> { - uri = - FileProvider.getUriForFile( - applicationContext, - Constants.FILE_PROVIDER_AUTHORITY, - attFile - ) + uri = Uri.fromFile(attFile) } attachmentEntity.fileUri == null -> { - FileAndDirectoryUtils.cleanDir(fwdAttsCacheDir) val inputStream = action.invoke(attachmentEntity) - val tempFile = File(fwdAttsCacheDir, UUID.randomUUID().toString()) + val tempFile = File(parentDirectory, UUID.randomUUID().toString()) if (inputStream != null) { inputStream.use { srcStream -> FileOutputStream(tempFile).use { destStream -> @@ -260,15 +275,11 @@ class DownloadForwardedAttachmentsWorker(context: Context, params: WorkerParamet } } - if (msgAttsDir.exists()) { + if (parentDirectory.exists()) { FileUtils.moveFile(tempFile, attFile) - uri = FileProvider.getUriForFile( - applicationContext, - Constants.FILE_PROVIDER_AUTHORITY, - attFile - ) + uri = Uri.fromFile(attFile) } else { - FileAndDirectoryUtils.cleanDir(fwdAttsCacheDir) + FileAndDirectoryUtils.cleanDir(parentDirectory) //It means the user has already deleted the current message. We don't need to download other attachments. break } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/service/ProcessingOutgoingMessageInfoHelper.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/service/ProcessingOutgoingMessageInfoHelper.kt index afe1ab8776..f1e57e0bb2 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/service/ProcessingOutgoingMessageInfoHelper.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/service/ProcessingOutgoingMessageInfoHelper.kt @@ -1,13 +1,12 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.service import android.content.Context import android.net.Uri -import androidx.core.content.FileProvider import com.flowcrypt.email.Constants import com.flowcrypt.email.api.email.EmailUtil import com.flowcrypt.email.api.email.model.AttachmentInfo @@ -145,9 +144,7 @@ object ProcessingOutgoingMessageInfoHelper { pubKeys = requireNotNull(pubKeys), fileName = originalAttName, ) - uri = FileProvider.getUriForFile( - context, Constants.FILE_PROVIDER_AUTHORITY, encryptedTempFile - ) + uri = Uri.fromFile(encryptedTempFile) name = encryptedTempFile.name } else { var cachedAtt = File(attsCacheDir, originalAttName) @@ -157,7 +154,7 @@ object ProcessingOutgoingMessageInfoHelper { } FileUtils.copyInputStreamToFile(originalFileInputStream, cachedAtt) - uri = FileProvider.getUriForFile(context, Constants.FILE_PROVIDER_AUTHORITY, cachedAtt) + uri = Uri.fromFile(cachedAtt) } cachedAtts.add( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/service/attachment/AttachmentDownloadManagerService.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/service/attachment/AttachmentDownloadManagerService.kt index b0604ede18..455b35d743 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/service/attachment/AttachmentDownloadManagerService.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/service/attachment/AttachmentDownloadManagerService.kt @@ -675,21 +675,36 @@ class AttachmentDownloadManagerService : LifecycleService() { */ private fun storeLegacy(attFile: File, context: Context): Uri { val fileName = finalFileName - val fileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - var sharedFile = File(fileDir, fileName) - sharedFile = if (sharedFile.exists()) { - FileAndDirectoryUtils.createFileWithIncreasedIndex(fileDir, fileName) + val flowCryptDirectoryForDownloadsName = "FlowCrypt" + val fileDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).apply { + if (!exists()) { + if (!mkdir()) { + error("Creating ${Environment.DIRECTORY_DOWNLOADS} failed!") + } + } + } + + val flowCryptDirectoryForDownloads = File(fileDir, flowCryptDirectoryForDownloadsName).apply { + if (!exists()) { + if (!mkdir()) { + error("Creating FlowCrypt directory in ${Environment.DIRECTORY_DOWNLOADS} failed!") + } + } + } + + var downloadedFile = File(flowCryptDirectoryForDownloads, fileName) + downloadedFile = if (downloadedFile.exists()) { + FileAndDirectoryUtils.createFileWithIncreasedIndex(flowCryptDirectoryForDownloads, fileName) } else { - sharedFile + downloadedFile } - finalFileName = sharedFile.name - val srcInputStream = attFile.inputStream() - val destOutputStream = FileUtils.openOutputStream(sharedFile) - srcInputStream.use { srcStream -> - destOutputStream.use { outStream -> srcStream.copyTo(outStream) } + finalFileName = downloadedFile.name + attFile.inputStream().use { srcStream -> + FileUtils.openOutputStream(downloadedFile).use { outStream -> srcStream.copyTo(outStream) } } - return FileProvider.getUriForFile(context, Constants.FILE_PROVIDER_AUTHORITY, sharedFile) + return FileProvider.getUriForFile(context, Constants.FILE_PROVIDER_AUTHORITY, downloadedFile) } fun setListener(listener: OnDownloadAttachmentListener) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt index fbd0e04db4..080626c8dc 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt @@ -1,6 +1,6 @@ /* * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 + * Contributors: denbond7 */ package com.flowcrypt.email.ui.activity @@ -13,6 +13,7 @@ import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.navigation.NavHostController import androidx.navigation.ui.AppBarConfiguration +import com.flowcrypt.email.Constants import com.flowcrypt.email.R import com.flowcrypt.email.api.email.model.AttachmentInfo import com.flowcrypt.email.api.email.model.IncomingMessageInfo @@ -26,7 +27,9 @@ import com.flowcrypt.email.extensions.toast import com.flowcrypt.email.model.MessageEncryptionType import com.flowcrypt.email.model.MessageType import com.flowcrypt.email.ui.activity.fragment.dialog.ChoosePublicKeyDialogFragment +import com.flowcrypt.email.util.FileAndDirectoryUtils import com.flowcrypt.email.util.FlavorSettings +import java.io.File /** * This activity describes a logic of send encrypted or standard message. @@ -71,6 +74,7 @@ class CreateMessageActivity : BaseActivity(), isNavigationArrowDisplayed = true val navGraph = navController.navInflater.inflate(R.navigation.create_msg_graph) navController.setGraph(navGraph, intent.extras) + FileAndDirectoryUtils.cleanDir(File(cacheDir, Constants.DRAFT_CACHE_DIR)) } override fun onAccountInfoRefreshed(accountEntity: AccountEntity?) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt index bffd9126ca..1650de257b 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt @@ -28,7 +28,6 @@ import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat import androidx.core.view.MenuHost @@ -109,6 +108,7 @@ import com.flowcrypt.email.ui.adapter.RecipientChipRecyclerViewAdapter import com.flowcrypt.email.ui.adapter.recyclerview.itemdecoration.MarginItemDecoration import com.flowcrypt.email.util.FileAndDirectoryUtils import com.flowcrypt.email.util.GeneralUtil +import com.flowcrypt.email.util.LogsUtil import com.flowcrypt.email.util.UIUtil import com.flowcrypt.email.util.exception.DecryptionException import com.flowcrypt.email.util.exception.ExceptionUtil @@ -576,7 +576,7 @@ class CreateMessageFragment : BaseFragment(), ) ) { this.extraActionInfo = ExtraActionInfo.parseExtraActionInfo(requireContext(), intent) - addAtts() + addAttachmentsFromExtras() } else { args.incomingMessageInfo?.localFolder?.let { this.folderType = FoldersManager.getFolderType(it) @@ -599,22 +599,20 @@ class CreateMessageFragment : BaseFragment(), } } - private fun addAtts() { + private fun addAttachmentsFromExtras() { val sizeWarningMsg = getString( R.string.template_warning_max_total_attachments_size, FileUtils.byteCountToDisplaySize(Constants.MAX_TOTAL_ATTACHMENT_SIZE_IN_BYTES) ) - extraActionInfo?.atts?.forEach { attachmentInfo -> - if (ContentResolver.SCHEME_FILE.equals(attachmentInfo.uri?.scheme, ignoreCase = true)) { - // we skip attachments that have SCHEME_FILE as deprecated - return - } - + extraActionInfo?.atts?.filter { + //we skip attachments that have SCHEME_FILE as prohibited(unsafe) + !ContentResolver.SCHEME_FILE.equals(it.uri?.scheme, ignoreCase = true) + }?.forEach { attachmentInfo -> + val uri = attachmentInfo.uri ?: return@forEach if (hasAbilityToAddAtt(attachmentInfo)) { - if (attachmentInfo.getSafeName().isEmpty()) { - val msg = "attachmentInfo.getName() is empty, uri = " + attachmentInfo.uri!! + val msg = "attachmentInfo.getName() is empty, uri = $uri" ExceptionUtil.handleError(NullPointerException(msg)) return } @@ -629,28 +627,22 @@ class CreateMessageFragment : BaseFragment(), } try { - val inputStream = requireContext().contentResolver.openInputStream(attachmentInfo.uri!!) - - if (inputStream != null) { + requireContext().contentResolver.openInputStream(uri)?.use { inputStream -> FileUtils.copyInputStreamToFile(inputStream, draftAtt) - val uri = FileProvider.getUriForFile( - requireContext(), - Constants.FILE_PROVIDER_AUTHORITY, - draftAtt + composeMsgViewModel.addAttachments( + listOf(attachmentInfo.copy(uri = Uri.fromFile(draftAtt))) ) - composeMsgViewModel.addAttachments(listOf(attachmentInfo.copy(uri = uri))) } } catch (e: IOException) { e.printStackTrace() ExceptionUtil.handleError(e) - if (!draftAtt.delete()) { - Log.e(TAG, "Delete " + draftAtt.name + " failed!") + LogsUtil.d(TAG, "Deleting ${draftAtt.name} failed!") } } } else { - Toast.makeText(context, sizeWarningMsg, Toast.LENGTH_SHORT).show() + toast(sizeWarningMsg, Toast.LENGTH_SHORT) return@forEach } } @@ -1621,7 +1613,7 @@ class CreateMessageFragment : BaseFragment(), return false } return (composeMsgViewModel.attachmentsStateFlow.value.isEmpty() - || !composeMsgViewModel.hasAttachmentsWithExternalStorageUri) + || !composeMsgViewModel.hasAttachmentsWittForeignExternalStorageUri) } private fun usePasswordIfNeeded(): CharArray? { @@ -1912,6 +1904,7 @@ class CreateMessageFragment : BaseFragment(), R.string.no_conn_msg_sent_later } ) + FileAndDirectoryUtils.cleanDir(draftCacheDir) activity?.finish() } diff --git a/FlowCrypt/src/main/res/xml/filepaths.xml b/FlowCrypt/src/main/res/xml/filepaths.xml index 99c095d737..a165f90d27 100644 --- a/FlowCrypt/src/main/res/xml/filepaths.xml +++ b/FlowCrypt/src/main/res/xml/filepaths.xml @@ -1,14 +1,15 @@ + path="Download/FlowCrypt" /> - + +