Implement workaround to open office files in MS apps in write mode
Fixes #150
This commit is contained in:
parent
e1a5ff7007
commit
7ce2e8e27c
@ -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<ApplicationComponent>
|
||||
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") }
|
||||
}
|
||||
|
||||
|
@ -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<out String> {
|
||||
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<List<CloudFile>, UploadState>() {
|
||||
override fun onProgress(progress: Progress<UploadState>) {
|
||||
view?.showProgress(progressModelMapper.toModel(progress))
|
||||
}
|
||||
|
||||
override fun onSuccess(files: List<CloudFile>) {
|
||||
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<CloudFileModel>
|
||||
get() {
|
||||
val previewCloudFiles = ArrayList<CloudFileModel>()
|
||||
@ -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<List<CloudFile>, UploadState>() {
|
||||
override fun onProgress(progress: Progress<UploadState>) {
|
||||
view?.showProgress(progressModelMapper.toModel(progress))
|
||||
}
|
||||
|
||||
override fun onSuccess(files: List<CloudFile>) {
|
||||
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<DownloadFile>)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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<MicrosoftWorkaroundDisclaimerDialog.Callback>() {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/activity_vertical_margin">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_microsoft_workaround_disclaimer_hint" />
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -54,4 +54,34 @@
|
||||
<item>30</item>
|
||||
<item>Never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="microsoft_extensions">
|
||||
<item>doc</item>
|
||||
<item>dot</item>
|
||||
<item>docx</item>
|
||||
<item>dotx</item>
|
||||
<item>docm</item>
|
||||
<item>dotm</item>
|
||||
<item>xls</item>
|
||||
<item>xlt</item>
|
||||
<item>xla</item>
|
||||
<item>xlsx</item>
|
||||
<item>xltx</item>
|
||||
<item>xlsm</item>
|
||||
<item>xltm</item>
|
||||
<item>xlam</item>
|
||||
<item>xlsb</item>
|
||||
<item>ppt</item>
|
||||
<item>pot</item>
|
||||
<item>pps</item>
|
||||
<item>ppa</item>
|
||||
<item>pptx</item>
|
||||
<item>potx</item>
|
||||
<item>ppsx</item>
|
||||
<item>ppam</item>
|
||||
<item>pptm</item>
|
||||
<item>potm</item>
|
||||
<item>ppsm</item>
|
||||
<item>mdb</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -275,6 +275,8 @@
|
||||
<string name="screen_settings_section_version">Version</string>
|
||||
|
||||
<string name="screen_settings_advanced_settings">Advanced Settings</string>
|
||||
<string name="screen_settings_microsoft_apps_workaround_label">Workaround opening Microsoft files</string>
|
||||
<string name="screen_settings_microsoft_apps_workaround_summary">Open files in Microsoft apps with write permission</string>
|
||||
<string name="screen_settings_background_unlock_preparation_label">Accelerate unlock</string>
|
||||
<string name="screen_settings_background_unlock_preparation_label_summary">Download vault config in the background while prompted to enter the password or biometric auth</string>
|
||||
<string name="screen_settings_keep_unlocked_while_editing_files">Keep unlocked</string>
|
||||
@ -409,6 +411,11 @@
|
||||
<string name="dialog_debug_mode_positive_button">Enable</string>
|
||||
<string name="dialog_debug_mode_negative_button" translatable="false">@string/dialog_button_cancel</string>
|
||||
|
||||
<string name="dialog_microsoft_workaround_disclaimer_hint">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>
|
||||
<string name="dialog_microsoft_workaround_disclaimer_title" translatable="false">@string/dialog_debug_mode_disclaimer_title</string>
|
||||
<string name="dialog_microsoft_workaround_positive_button" translatable="false">@string/dialog_debug_mode_positive_button</string>
|
||||
<string name="dialog_microsoft_workaround_negative_button" translatable="false">@string/dialog_button_cancel</string>
|
||||
|
||||
<string name="dialog_disable_app_obscured_disclaimer_hint">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 <a href="https://docs.cryptomator.org/en/1.5/android/settings/#block-app-when-obscured">aware of the risks</a>.</string>
|
||||
<string name="dialog_disable_app_obscured_disclaimer_title">Attention</string>
|
||||
<string name="dialog_disable_app_obscured_positive_button">Disable</string>
|
||||
|
@ -181,6 +181,12 @@
|
||||
|
||||
<PreferenceCategory android:title="@string/screen_settings_advanced_settings">
|
||||
|
||||
<androidx.preference.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="shareOfficeFilePublicly"
|
||||
android:summary="@string/screen_settings_microsoft_apps_workaround_summary"
|
||||
android:title="@string/screen_settings_microsoft_apps_workaround_label" />
|
||||
|
||||
<androidx.preference.SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="backgroundUnlockPreparation"
|
||||
|
@ -261,6 +261,14 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen
|
||||
}
|
||||
}
|
||||
|
||||
fun setMicrosoftWorkaround(enabled: Boolean) {
|
||||
defaultSharedPreferences.setValue(MICROSOFT_WORKAROUND, enabled)
|
||||
}
|
||||
|
||||
fun microsoftWorkaround(): Boolean {
|
||||
return defaultSharedPreferences.getBoolean(MICROSOFT_WORKAROUND, false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val SCREEN_LOCK_DIALOG_SHOWN = "askForScreenLockDialogShown"
|
||||
@ -287,6 +295,7 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen
|
||||
const val PHOTO_UPLOAD_INCLUDING_VIDEOS = "photoUploadIncludingVideos"
|
||||
const val USE_LRU_CACHE = "lruCache"
|
||||
const val LRU_CACHE_SIZE = "lruCacheSize"
|
||||
const val MICROSOFT_WORKAROUND = "shareOfficeFilePublicly"
|
||||
const val MAIL = "mail"
|
||||
const val UPDATE_INTERVAL = "updateInterval"
|
||||
private const val LAST_UPDATE_CHECK = "lastUpdateCheck"
|
||||
|
Loading…
x
Reference in New Issue
Block a user