From 7ce2e8e27c8904cb5722f563b15a444416bc464c Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Mon, 28 Feb 2022 22:09:34 +0100 Subject: [PATCH] Implement workaround to open office files in MS apps in write mode Fixes #150 --- .../presentation/CryptomatorApp.kt | 6 + .../presenter/BrowseFilesPresenter.kt | 219 ++++++++++-------- .../ui/activity/BrowseFilesActivity.kt | 9 - .../ui/activity/SettingsActivity.kt | 14 +- .../MicrosoftWorkaroundDisclaimerDialog.kt | 41 ++++ .../ui/fragment/SettingsFragment.kt | 25 +- .../cryptomator/presentation/util/FileUtil.kt | 25 +- ...dialog_microsoft_workaround_disclaimer.xml | 18 ++ presentation/src/main/res/values/arrays.xml | 30 +++ presentation/src/main/res/values/strings.xml | 7 + presentation/src/main/res/xml/preferences.xml | 6 + .../util/SharedPreferencesHandler.kt | 9 + 12 files changed, 296 insertions(+), 113 deletions(-) create mode 100644 presentation/src/main/java/org/cryptomator/presentation/ui/dialog/MicrosoftWorkaroundDisclaimerDialog.kt create mode 100644 presentation/src/main/res/layout/dialog_microsoft_workaround_disclaimer.xml diff --git a/presentation/src/main/java/org/cryptomator/presentation/CryptomatorApp.kt b/presentation/src/main/java/org/cryptomator/presentation/CryptomatorApp.kt index 3ba4a15e..d532fb95 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/CryptomatorApp.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/CryptomatorApp.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.ServiceConnection import android.os.Build import android.os.IBinder +import android.os.StrictMode import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDexApplication import org.cryptomator.data.cloud.crypto.Cryptors @@ -67,6 +68,11 @@ class CryptomatorApp : MultiDexApplication(), HasComponent AppCompatDelegate.setDefaultNightMode(SharedPreferencesHandler(applicationContext()).screenStyleMode) cleanupCache() + if (SharedPreferencesHandler(applicationContext()).microsoftWorkaround()) { + val builder: StrictMode.VmPolicy.Builder = StrictMode.VmPolicy.Builder() + StrictMode.setVmPolicy(builder.build()) + } + RxJavaPlugins.setErrorHandler { e: Throwable? -> Timber.tag("CryptomatorApp").e(e, "BaseErrorHandler detected a problem") } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt index 9ff476f8..867e6cbf 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.net.Uri import android.provider.DocumentsContract import android.widget.Toast +import androidx.core.net.toFile import org.cryptomator.data.cloud.crypto.CryptoFolder import org.cryptomator.domain.Cloud import org.cryptomator.domain.CloudFile @@ -232,8 +233,8 @@ class BrowseFilesPresenter @Inject constructor( // @Callback(dispatchResultOkOnly = false) fun getCloudListAfterAuthentication(result: ActivityResult, cloudFolderModel: CloudFolderModel) { - if(result.isResultOk) { - val cloudModel = result.getSingleResult(CloudModel::class.java) // FIXME update other vaults using this cloud as well + if (result.isResultOk) { + val cloudModel = result.getSingleResult(CloudModel::class.java) val cloudNode = cloudFolderModel.toCloudNode() if (cloudNode is CryptoFolder) { updatedDecryptedCloudFor(Vault.aCopyOf(cloudFolderModel.vault()!!.toVault()).withCloud(cloudModel.toCloud()).build(), cloudFolderModel) @@ -263,6 +264,7 @@ class BrowseFilesPresenter @Inject constructor( // view?.updateActiveFolderDueToAuthenticationProblem(folder) getCloudList(folder) } + override fun onFinished() { resumedAfterAuthentication = false } @@ -529,14 +531,16 @@ class BrowseFilesPresenter @Inject constructor( // private fun viewExternalFile(cloudFile: CloudFileModel) { val viewFileIntent = Intent(Intent.ACTION_VIEW) - fileUtil.contentUriFor(cloudFile).let { - uriToOpenedFile = it + var openFileType = OpenFileType.DEFAULT + uriToOpenedFile = if (useMicrosoftWorkaround(cloudFile)) { + openFileType = OpenFileType.MICROSOFT_WORKAROUND + Uri.fromFile(fileUtil.getLegacyFileForMicrosoftWorkaround(cloudFile)) + } else { + fileUtil.contentUriFor(cloudFile) + }.also { openedCloudFile = cloudFile openedCloudFileMd5 = calculateDigestFromUri(it) - viewFileIntent.setDataAndType( // - uriToOpenedFile, // - mimeTypes.fromFilename(cloudFile.name)?.toString() - ) + viewFileIntent.setDataAndType(uriToOpenedFile, mimeTypes.fromFilename(cloudFile.name)?.toString()) viewFileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) if (sharedPreferencesHandler.keepUnlockedWhileEditing()) { openWritableFileNotification = OpenWritableFileNotification(context(), it) @@ -544,10 +548,119 @@ class BrowseFilesPresenter @Inject constructor( // val cryptomatorApp = activity().application as CryptomatorApp cryptomatorApp.suspendLock() } - activity().startActivityForResult(viewFileIntent, OPEN_FILE_FINISHED) + requestActivityResult(ActivityResultCallbacks.openFileFinished(openFileType), viewFileIntent) } } + enum class OpenFileType { + DEFAULT, MICROSOFT_WORKAROUND + } + + private fun useMicrosoftWorkaround(cloudFile: CloudFileModel): Boolean { + return sharedPreferencesHandler.microsoftWorkaround() + && cloudFile.name.contains(".") + && microsoftExtensions().contains(cloudFile.name.substring(cloudFile.name.lastIndexOf(".") + 1, cloudFile.name.length)) + } + + private fun microsoftExtensions(): Array { + return context().resources.getStringArray(R.array.microsoft_extensions); + } + + @Callback(dispatchResultOkOnly = false) + fun openFileFinished(result: ActivityResult, openFileType: OpenFileType) { + try { + // necessary see https://community.cryptomator.org/t/android-tabelle-nach-upload-unlesbar/6550 + Thread.sleep(500) + } catch (e: InterruptedException) { + Timber.tag("BrowseFilesPresenter").e(e, "Failed to sleep after resuming editing, necessary for google office apps") + } + if (sharedPreferencesHandler.keepUnlockedWhileEditing()) { + val cryptomatorApp = activity().application as CryptomatorApp + cryptomatorApp.unSuspendLock() + } + hideWritableNotification() + + context().revokeUriPermission(uriToOpenedFile, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) + + uriToOpenedFile?.let { + try { + calculateDigestFromUri(it)?.let { hashAfterEdit -> + openedCloudFileMd5?.let { hashBeforeEdit -> + if (hashAfterEdit.contentEquals(hashBeforeEdit)) { + Timber.tag("BrowseFilesPresenter").i("Opened app finished, file not changed") + deleteFileIfMicrosoftWorkaround(openFileType, uriToOpenedFile) + } else { + uploadChangedFile(openFileType) + } + } ?: deleteFileIfMicrosoftWorkaround(openFileType, uriToOpenedFile) + } + } catch (e: FileNotFoundException) { + Timber.tag("BrowseFilesPresenter").e(e, "Failed to read back changes, file isn't present anymore") + Toast.makeText(context(), R.string.error_file_not_found_after_opening_using_3party, Toast.LENGTH_LONG).show() + } + } + } + + private fun uploadChangedFile(openFileType: OpenFileType) { + view?.showUploadDialog(1) + openedCloudFile?.let { openedCloudFile -> + openedCloudFile.parent?.let { openedCloudFilesParent -> + uriToOpenedFile?.let { uriToOpenedFile -> + uploadFilesUseCase // + .withParent(openedCloudFilesParent.toCloudNode()) // + .andFiles(listOf(createUploadFile(openedCloudFile.name, uriToOpenedFile, true))) // + .run(object : DefaultProgressAwareResultHandler, UploadState>() { + override fun onProgress(progress: Progress) { + view?.showProgress(progressModelMapper.toModel(progress)) + } + + override fun onSuccess(files: List) { + files.forEach { file -> + view?.addOrUpdateCloudNode(cloudFileModelMapper.toModel(file)) + } + deleteFileIfMicrosoftWorkaround(openFileType, uriToOpenedFile) + onFileUploadCompleted() + } + + override fun onError(e: Throwable) { + onFileUploadError() + if (ExceptionUtil.contains(e, CloudNodeAlreadyExistsException::class.java)) { + ExceptionUtil.extract(e, CloudNodeAlreadyExistsException::class.java).get().message?.let { + onCloudNodeAlreadyExists(it) + } ?: super.onError(e) + } else { + super.onError(e) + } + } + }) + } + } + } + } + + private fun deleteFileIfMicrosoftWorkaround(openFileType: OpenFileType, uriToOpenedFile: Uri?) { + if (openFileType == OpenFileType.MICROSOFT_WORKAROUND) { + uriToOpenedFile?.toFile()?.delete() + } + } + + private fun hideWritableNotification() { + // openWritableFileNotification can not be made serializable because of this, can be null after Activity resumed + openWritableFileNotification?.hide() ?: OpenWritableFileNotification(context(), Uri.EMPTY).hide() + } + + @Throws(FileNotFoundException::class) + private fun calculateDigestFromUri(uri: Uri): ByteArray? { + val digest = MessageDigest.getInstance("MD5") + DigestInputStream(context().contentResolver.openInputStream(uri), digest).use { dis -> + val buffer = ByteArray(4096) + // Read all bytes: + while (dis.read(buffer) > -1) { + } + } + return digest.digest() + } + private val previewCloudFileNodes: ArrayList get() { val previewCloudFiles = ArrayList() @@ -1154,7 +1267,7 @@ class BrowseFilesPresenter @Inject constructor( // } fun onFolderReloadContent(folder: CloudFolderModel) { - if(!resumedAfterAuthentication) { + if (!resumedAfterAuthentication) { getCloudList(folder) } } @@ -1174,92 +1287,6 @@ class BrowseFilesPresenter @Inject constructor( // activity().invalidateOptionsMenu() } - fun openFileFinished() { - try { - // necessary see https://community.cryptomator.org/t/android-tabelle-nach-upload-unlesbar/6550 - Thread.sleep(500) - } catch (e: InterruptedException) { - Timber.tag("BrowseFilesPresenter").e(e, "Failed to sleep after resuming editing, necessary for google office apps") - } - if (sharedPreferencesHandler.keepUnlockedWhileEditing()) { - val cryptomatorApp = activity().application as CryptomatorApp - cryptomatorApp.unSuspendLock() - } - hideWritableNotification() - - context().revokeUriPermission(uriToOpenedFile, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) - - uriToOpenedFile?.let { - try { - calculateDigestFromUri(it)?.let { hashAfterEdit -> - openedCloudFileMd5?.let { hashBeforeEdit -> - if (hashAfterEdit.contentEquals(hashBeforeEdit)) { - Timber.tag("BrowseFilesPresenter").i("Opened app finished, file not changed") - } else { - uploadChangedFile() - } - } - } - } catch (e: FileNotFoundException) { - Timber.tag("BrowseFilesPresenter").e(e, "Failed to read back changes, file isn't present anymore") - Toast.makeText(context(), R.string.error_file_not_found_after_opening_using_3party, Toast.LENGTH_LONG).show() - } - } - } - - private fun uploadChangedFile() { - view?.showUploadDialog(1) - openedCloudFile?.let { openedCloudFile -> - openedCloudFile.parent?.let { openedCloudFilesParent -> - uriToOpenedFile?.let { uriToOpenedFile -> - uploadFilesUseCase // - .withParent(openedCloudFilesParent.toCloudNode()) // - .andFiles(listOf(createUploadFile(openedCloudFile.name, uriToOpenedFile, true))) // - .run(object : DefaultProgressAwareResultHandler, UploadState>() { - override fun onProgress(progress: Progress) { - view?.showProgress(progressModelMapper.toModel(progress)) - } - - override fun onSuccess(files: List) { - files.forEach { file -> - view?.addOrUpdateCloudNode(cloudFileModelMapper.toModel(file)) - } - onFileUploadCompleted() - } - - override fun onError(e: Throwable) { - onFileUploadError() - if (ExceptionUtil.contains(e, CloudNodeAlreadyExistsException::class.java)) { - ExceptionUtil.extract(e, CloudNodeAlreadyExistsException::class.java).get().message?.let { - onCloudNodeAlreadyExists(it) - } ?: super.onError(e) - } else { - super.onError(e) - } - } - }) - } - } - } - } - - private fun hideWritableNotification() { - // openWritableFileNotification can not be made serializable because of this, can be null after Activity resumed - openWritableFileNotification?.hide() ?: OpenWritableFileNotification(context(), Uri.EMPTY).hide() - } - - @Throws(FileNotFoundException::class) - private fun calculateDigestFromUri(uri: Uri): ByteArray? { - val digest = MessageDigest.getInstance("MD5") - DigestInputStream(context().contentResolver.openInputStream(uri), digest).use { dis -> - val buffer = ByteArray(8192) - // Read all bytes: - while (dis.read(buffer) > -1) { - } - } - return digest.digest() - } - interface ExportOperation : Serializable { fun export(presenter: BrowseFilesPresenter, downloadFiles: List) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt index 6ebb9189..748f86aa 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt @@ -32,7 +32,6 @@ import org.cryptomator.presentation.model.comparator.CloudNodeModelNameZACompara import org.cryptomator.presentation.model.comparator.CloudNodeModelSizeBiggestFirstComparator import org.cryptomator.presentation.model.comparator.CloudNodeModelSizeSmallestFirstComparator import org.cryptomator.presentation.presenter.BrowseFilesPresenter -import org.cryptomator.presentation.presenter.BrowseFilesPresenter.Companion.OPEN_FILE_FINISHED import org.cryptomator.presentation.service.CryptorsService import org.cryptomator.presentation.ui.activity.view.BrowseFilesView import org.cryptomator.presentation.ui.bottomsheet.FileSettingsBottomSheet @@ -249,14 +248,6 @@ class BrowseFilesActivity : BaseActivity(), // else -> super.onMenuItemSelected(itemId) } - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - super.onActivityResult(requestCode, resultCode, intent) - - if (requestCode == OPEN_FILE_FINISHED) { - browseFilesPresenter.openFileFinished() - } - } - override fun onPrepareOptionsMenu(menu: Menu): Boolean { if (isNavigationMode(SELECT_ITEMS)) { menu.findItem(R.id.action_delete_items).isEnabled = enableGeneralSelectionActions diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt index b69f4c1e..0a64940a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt @@ -11,6 +11,7 @@ import org.cryptomator.presentation.ui.activity.view.SettingsView import org.cryptomator.presentation.ui.dialog.DebugModeDisclaimerDialog import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog import org.cryptomator.presentation.ui.dialog.DisableSecureScreenDisclaimerDialog +import org.cryptomator.presentation.ui.dialog.MicrosoftWorkaroundDisclaimerDialog import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog import org.cryptomator.presentation.ui.dialog.UpdateAppDialog import org.cryptomator.presentation.ui.fragment.SettingsFragment @@ -23,8 +24,9 @@ class SettingsActivity : BaseActivity(), DebugModeDisclaimerDialog.Callback, DisableAppWhenObscuredDisclaimerDialog.Callback, DisableSecureScreenDisclaimerDialog.Callback, - UpdateAppAvailableDialog.Callback, // - UpdateAppDialog.Callback { + UpdateAppAvailableDialog.Callback, + UpdateAppDialog.Callback, + MicrosoftWorkaroundDisclaimerDialog.Callback { @Inject lateinit var presenter: SettingsPresenter @@ -101,4 +103,12 @@ class SettingsActivity : BaseActivity(), override fun onUpdateAppDialogLoaded() { showProgress(ProgressModel.GENERIC) } + + override fun onMicrosoftDisclaimerAccepted() { + presenter.onDebugModeChanged(accepted()) + } + + override fun onMicrosoftDisclaimerRejected() { + settingsFragment().deactivateMicrosoftWorkaround() + } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/MicrosoftWorkaroundDisclaimerDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/MicrosoftWorkaroundDisclaimerDialog.kt new file mode 100644 index 00000000..bcdfba6e --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/MicrosoftWorkaroundDisclaimerDialog.kt @@ -0,0 +1,41 @@ +package org.cryptomator.presentation.ui.dialog + +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import org.cryptomator.generator.Dialog +import org.cryptomator.presentation.R + +@Dialog(R.layout.dialog_microsoft_workaround_disclaimer) +class MicrosoftWorkaroundDisclaimerDialog : BaseDialog() { + + interface Callback { + + fun onMicrosoftDisclaimerAccepted() + fun onMicrosoftDisclaimerRejected() + } + + public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { + builder // + .setTitle(R.string.dialog_microsoft_workaround_disclaimer_title) // + .setPositiveButton(getString(R.string.dialog_microsoft_workaround_positive_button)) { _: DialogInterface, _: Int -> callback?.onMicrosoftDisclaimerAccepted() } // + .setNegativeButton(getString(R.string.dialog_microsoft_workaround_negative_button)) { _: DialogInterface, _: Int -> callback?.onMicrosoftDisclaimerRejected() } + return builder.create() + } + + public override fun setupView() { + // empty + } + + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + callback?.onMicrosoftDisclaimerRejected() + } + + companion object { + + fun newInstance(): DialogFragment { + return MicrosoftWorkaroundDisclaimerDialog() + } + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt index d40769ff..c5176821 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt @@ -19,6 +19,7 @@ import org.cryptomator.presentation.ui.activity.SettingsActivity import org.cryptomator.presentation.ui.dialog.DebugModeDisclaimerDialog import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog import org.cryptomator.presentation.ui.dialog.DisableSecureScreenDisclaimerDialog +import org.cryptomator.presentation.ui.dialog.MicrosoftWorkaroundDisclaimerDialog import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.file.LruFileCacheUtil import java.lang.Boolean.FALSE @@ -95,6 +96,11 @@ class SettingsFragment : PreferenceFragmentCompat() { true } + private val microsoftWorkaroundChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + onMicrosoftWorkaroundChanged(TRUE == newValue) + true + } + private fun activity(): SettingsActivity = this.activity as SettingsActivity private fun isBiometricAuthenticationNotAvailableRemovePreference() { @@ -206,6 +212,7 @@ class SettingsFragment : PreferenceFragmentCompat() { (findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as Preference?)?.onPreferenceChangeListener = useAutoPhotoUploadChangedListener (findPreference(SharedPreferencesHandler.USE_LRU_CACHE) as Preference?)?.onPreferenceChangeListener = useLruChangedListener (findPreference(SharedPreferencesHandler.LRU_CACHE_SIZE) as Preference?)?.onPreferenceChangeListener = useLruChangedListener + (findPreference(SharedPreferencesHandler.MICROSOFT_WORKAROUND) as Preference?)?.onPreferenceChangeListener = microsoftWorkaroundChangeListener if (BuildConfig.FLAVOR == "apkstore") { (findPreference(UPDATE_CHECK_ITEM_KEY) as Preference?)?.onPreferenceClickListener = updateCheckClickListener } @@ -263,14 +270,24 @@ class SettingsFragment : PreferenceFragmentCompat() { (findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as SwitchPreferenceCompat?)?.isChecked = enabled } - fun rootView(): View { - return activity().findViewById(R.id.activityRootView) - } - fun disableAutoUpload() { onUseAutoPhotoUploadChanged(false) } + private fun onMicrosoftWorkaroundChanged(enabled: Boolean) { + if (enabled) { + activity().showDialog(MicrosoftWorkaroundDisclaimerDialog.newInstance()) + } + } + + fun deactivateMicrosoftWorkaround() { + sharedPreferencesHandler.setMicrosoftWorkaround(false) + } + + fun rootView(): View { + return activity().findViewById(R.id.activityRootView) + } + companion object { private const val APP_VERSION_ITEM_KEY = "appVersion" diff --git a/presentation/src/main/java/org/cryptomator/presentation/util/FileUtil.kt b/presentation/src/main/java/org/cryptomator/presentation/util/FileUtil.kt index 900b128f..2c80676b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/util/FileUtil.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/util/FileUtil.kt @@ -20,8 +20,6 @@ import java.io.InvalidClassException import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.io.OutputStream -import java.util.ArrayList -import java.util.HashSet import javax.inject.Inject import timber.log.Timber @@ -114,6 +112,29 @@ class FileUtil @Inject constructor(private val context: Context, private val mim return FileInfo(name, mimeTypes) } + fun getLegacyFileForMicrosoftWorkaround(cloudFile: CloudFileModel): File { + return getPublicDecryptedFileStorage()?.let { + val publicOfficeFile = File(it, fileNameLowerCaseExtension(cloudFile)) + fileFor(cloudFile).copyTo(publicOfficeFile, true) + } ?: fileFor(cloudFile) + } + + private fun getPublicDecryptedFileStorage(): File? { + return try { + val mediaDir = context.getExternalMediaDirs().first { dir -> dir.startsWith("/storage/emulated/0") } + val publicDecryptedFileStorage = File(mediaDir, "decrypted") + if (publicDecryptedFileStorage.canWrite()) { + publicDecryptedFileStorage + } else { + Timber.tag("FileUtil").e("Media storage isn't writable") + null + } + } catch (e: NoSuchElementException) { + Timber.tag("FileUtil").e("Media storage isn't available") + null + } + } + fun storeImagePreviewFiles(imagePreviewFilesStore: ImagePreviewFilesStore?): String? { decryptedFileStorage.mkdir() val file = File(decryptedFileStorage, IMAGE_PREVIEW__FILE_NAMES) diff --git a/presentation/src/main/res/layout/dialog_microsoft_workaround_disclaimer.xml b/presentation/src/main/res/layout/dialog_microsoft_workaround_disclaimer.xml new file mode 100644 index 00000000..231fb6e0 --- /dev/null +++ b/presentation/src/main/res/layout/dialog_microsoft_workaround_disclaimer.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/presentation/src/main/res/values/arrays.xml b/presentation/src/main/res/values/arrays.xml index 9998d544..ca4f403f 100644 --- a/presentation/src/main/res/values/arrays.xml +++ b/presentation/src/main/res/values/arrays.xml @@ -54,4 +54,34 @@ 30 Never + + + doc + dot + docx + dotx + docm + dotm + xls + xlt + xla + xlsx + xltx + xlsm + xltm + xlam + xlsb + ppt + pot + pps + ppa + pptx + potx + ppsx + ppam + pptm + potm + ppsm + mdb + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 8af0409d..3a38bad2 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -275,6 +275,8 @@ Version Advanced Settings + Workaround opening Microsoft files + Open files in Microsoft apps with write permission Accelerate unlock Download vault config in the background while prompted to enter the password or biometric auth Keep unlocked @@ -409,6 +411,11 @@ Enable @string/dialog_button_cancel + Due to a bug in Microsoft apps, we need to share the file with them in a public media folder. We only use this using those file types. After resuming to Cryptomator, we delete the public available file again but we can not influence what happens to this file in the meantime. So just enable it if this isn\'t a problem for you.\n\nIf enabled, please restart Cryptomator to apply this change. + @string/dialog_debug_mode_disclaimer_title + @string/dialog_debug_mode_positive_button + @string/dialog_button_cancel + This setting is a security feature and prevents other apps from tricking users into doing things they do not wan\'t to do.\n\nBy disabling, you confirm that you are aware of the risks. Attention Disable diff --git a/presentation/src/main/res/xml/preferences.xml b/presentation/src/main/res/xml/preferences.xml index be07dbd7..540f255e 100644 --- a/presentation/src/main/res/xml/preferences.xml +++ b/presentation/src/main/res/xml/preferences.xml @@ -181,6 +181,12 @@ + +