Implement workaround to open office files in MS apps in write mode

Fixes #150
This commit is contained in:
Julian Raufelder 2022-02-28 22:09:34 +01:00
parent e1a5ff7007
commit 7ce2e8e27c
No known key found for this signature in database
GPG Key ID: 17EE71F6634E381D
12 changed files with 296 additions and 113 deletions

View File

@ -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") }
}

View File

@ -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>)

View File

@ -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

View File

@ -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()
}
}

View File

@ -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()
}
}
}

View File

@ -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"

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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"