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.content.ServiceConnection
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.StrictMode
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import org.cryptomator.data.cloud.crypto.Cryptors
|
import org.cryptomator.data.cloud.crypto.Cryptors
|
||||||
@ -67,6 +68,11 @@ class CryptomatorApp : MultiDexApplication(), HasComponent<ApplicationComponent>
|
|||||||
AppCompatDelegate.setDefaultNightMode(SharedPreferencesHandler(applicationContext()).screenStyleMode)
|
AppCompatDelegate.setDefaultNightMode(SharedPreferencesHandler(applicationContext()).screenStyleMode)
|
||||||
cleanupCache()
|
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") }
|
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.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.net.toFile
|
||||||
import org.cryptomator.data.cloud.crypto.CryptoFolder
|
import org.cryptomator.data.cloud.crypto.CryptoFolder
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.CloudFile
|
import org.cryptomator.domain.CloudFile
|
||||||
@ -232,8 +233,8 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
|
|
||||||
@Callback(dispatchResultOkOnly = false)
|
@Callback(dispatchResultOkOnly = false)
|
||||||
fun getCloudListAfterAuthentication(result: ActivityResult, cloudFolderModel: CloudFolderModel) {
|
fun getCloudListAfterAuthentication(result: ActivityResult, cloudFolderModel: CloudFolderModel) {
|
||||||
if(result.isResultOk) {
|
if (result.isResultOk) {
|
||||||
val cloudModel = result.getSingleResult(CloudModel::class.java) // FIXME update other vaults using this cloud as well
|
val cloudModel = result.getSingleResult(CloudModel::class.java)
|
||||||
val cloudNode = cloudFolderModel.toCloudNode()
|
val cloudNode = cloudFolderModel.toCloudNode()
|
||||||
if (cloudNode is CryptoFolder) {
|
if (cloudNode is CryptoFolder) {
|
||||||
updatedDecryptedCloudFor(Vault.aCopyOf(cloudFolderModel.vault()!!.toVault()).withCloud(cloudModel.toCloud()).build(), cloudFolderModel)
|
updatedDecryptedCloudFor(Vault.aCopyOf(cloudFolderModel.vault()!!.toVault()).withCloud(cloudModel.toCloud()).build(), cloudFolderModel)
|
||||||
@ -263,6 +264,7 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
view?.updateActiveFolderDueToAuthenticationProblem(folder)
|
view?.updateActiveFolderDueToAuthenticationProblem(folder)
|
||||||
getCloudList(folder)
|
getCloudList(folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinished() {
|
override fun onFinished() {
|
||||||
resumedAfterAuthentication = false
|
resumedAfterAuthentication = false
|
||||||
}
|
}
|
||||||
@ -529,14 +531,16 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
|
|
||||||
private fun viewExternalFile(cloudFile: CloudFileModel) {
|
private fun viewExternalFile(cloudFile: CloudFileModel) {
|
||||||
val viewFileIntent = Intent(Intent.ACTION_VIEW)
|
val viewFileIntent = Intent(Intent.ACTION_VIEW)
|
||||||
fileUtil.contentUriFor(cloudFile).let {
|
var openFileType = OpenFileType.DEFAULT
|
||||||
uriToOpenedFile = it
|
uriToOpenedFile = if (useMicrosoftWorkaround(cloudFile)) {
|
||||||
|
openFileType = OpenFileType.MICROSOFT_WORKAROUND
|
||||||
|
Uri.fromFile(fileUtil.getLegacyFileForMicrosoftWorkaround(cloudFile))
|
||||||
|
} else {
|
||||||
|
fileUtil.contentUriFor(cloudFile)
|
||||||
|
}.also {
|
||||||
openedCloudFile = cloudFile
|
openedCloudFile = cloudFile
|
||||||
openedCloudFileMd5 = calculateDigestFromUri(it)
|
openedCloudFileMd5 = calculateDigestFromUri(it)
|
||||||
viewFileIntent.setDataAndType( //
|
viewFileIntent.setDataAndType(uriToOpenedFile, mimeTypes.fromFilename(cloudFile.name)?.toString())
|
||||||
uriToOpenedFile, //
|
|
||||||
mimeTypes.fromFilename(cloudFile.name)?.toString()
|
|
||||||
)
|
|
||||||
viewFileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
viewFileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
if (sharedPreferencesHandler.keepUnlockedWhileEditing()) {
|
if (sharedPreferencesHandler.keepUnlockedWhileEditing()) {
|
||||||
openWritableFileNotification = OpenWritableFileNotification(context(), it)
|
openWritableFileNotification = OpenWritableFileNotification(context(), it)
|
||||||
@ -544,10 +548,119 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
val cryptomatorApp = activity().application as CryptomatorApp
|
val cryptomatorApp = activity().application as CryptomatorApp
|
||||||
cryptomatorApp.suspendLock()
|
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>
|
private val previewCloudFileNodes: ArrayList<CloudFileModel>
|
||||||
get() {
|
get() {
|
||||||
val previewCloudFiles = ArrayList<CloudFileModel>()
|
val previewCloudFiles = ArrayList<CloudFileModel>()
|
||||||
@ -1154,7 +1267,7 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onFolderReloadContent(folder: CloudFolderModel) {
|
fun onFolderReloadContent(folder: CloudFolderModel) {
|
||||||
if(!resumedAfterAuthentication) {
|
if (!resumedAfterAuthentication) {
|
||||||
getCloudList(folder)
|
getCloudList(folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1174,92 +1287,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
activity().invalidateOptionsMenu()
|
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 {
|
interface ExportOperation : Serializable {
|
||||||
|
|
||||||
fun export(presenter: BrowseFilesPresenter, downloadFiles: List<DownloadFile>)
|
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.CloudNodeModelSizeBiggestFirstComparator
|
||||||
import org.cryptomator.presentation.model.comparator.CloudNodeModelSizeSmallestFirstComparator
|
import org.cryptomator.presentation.model.comparator.CloudNodeModelSizeSmallestFirstComparator
|
||||||
import org.cryptomator.presentation.presenter.BrowseFilesPresenter
|
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.service.CryptorsService
|
||||||
import org.cryptomator.presentation.ui.activity.view.BrowseFilesView
|
import org.cryptomator.presentation.ui.activity.view.BrowseFilesView
|
||||||
import org.cryptomator.presentation.ui.bottomsheet.FileSettingsBottomSheet
|
import org.cryptomator.presentation.ui.bottomsheet.FileSettingsBottomSheet
|
||||||
@ -249,14 +248,6 @@ class BrowseFilesActivity : BaseActivity(), //
|
|||||||
else -> super.onMenuItemSelected(itemId)
|
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 {
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
if (isNavigationMode(SELECT_ITEMS)) {
|
if (isNavigationMode(SELECT_ITEMS)) {
|
||||||
menu.findItem(R.id.action_delete_items).isEnabled = enableGeneralSelectionActions
|
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.DebugModeDisclaimerDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog
|
import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.DisableSecureScreenDisclaimerDialog
|
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.UpdateAppAvailableDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
|
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
|
||||||
import org.cryptomator.presentation.ui.fragment.SettingsFragment
|
import org.cryptomator.presentation.ui.fragment.SettingsFragment
|
||||||
@ -23,8 +24,9 @@ class SettingsActivity : BaseActivity(),
|
|||||||
DebugModeDisclaimerDialog.Callback,
|
DebugModeDisclaimerDialog.Callback,
|
||||||
DisableAppWhenObscuredDisclaimerDialog.Callback,
|
DisableAppWhenObscuredDisclaimerDialog.Callback,
|
||||||
DisableSecureScreenDisclaimerDialog.Callback,
|
DisableSecureScreenDisclaimerDialog.Callback,
|
||||||
UpdateAppAvailableDialog.Callback, //
|
UpdateAppAvailableDialog.Callback,
|
||||||
UpdateAppDialog.Callback {
|
UpdateAppDialog.Callback,
|
||||||
|
MicrosoftWorkaroundDisclaimerDialog.Callback {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: SettingsPresenter
|
lateinit var presenter: SettingsPresenter
|
||||||
@ -101,4 +103,12 @@ class SettingsActivity : BaseActivity(),
|
|||||||
override fun onUpdateAppDialogLoaded() {
|
override fun onUpdateAppDialogLoaded() {
|
||||||
showProgress(ProgressModel.GENERIC)
|
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.DebugModeDisclaimerDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog
|
import org.cryptomator.presentation.ui.dialog.DisableAppWhenObscuredDisclaimerDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.DisableSecureScreenDisclaimerDialog
|
import org.cryptomator.presentation.ui.dialog.DisableSecureScreenDisclaimerDialog
|
||||||
|
import org.cryptomator.presentation.ui.dialog.MicrosoftWorkaroundDisclaimerDialog
|
||||||
import org.cryptomator.util.SharedPreferencesHandler
|
import org.cryptomator.util.SharedPreferencesHandler
|
||||||
import org.cryptomator.util.file.LruFileCacheUtil
|
import org.cryptomator.util.file.LruFileCacheUtil
|
||||||
import java.lang.Boolean.FALSE
|
import java.lang.Boolean.FALSE
|
||||||
@ -95,6 +96,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val microsoftWorkaroundChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
onMicrosoftWorkaroundChanged(TRUE == newValue)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
private fun activity(): SettingsActivity = this.activity as SettingsActivity
|
private fun activity(): SettingsActivity = this.activity as SettingsActivity
|
||||||
|
|
||||||
private fun isBiometricAuthenticationNotAvailableRemovePreference() {
|
private fun isBiometricAuthenticationNotAvailableRemovePreference() {
|
||||||
@ -206,6 +212,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as Preference?)?.onPreferenceChangeListener = useAutoPhotoUploadChangedListener
|
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as Preference?)?.onPreferenceChangeListener = useAutoPhotoUploadChangedListener
|
||||||
(findPreference(SharedPreferencesHandler.USE_LRU_CACHE) as Preference?)?.onPreferenceChangeListener = useLruChangedListener
|
(findPreference(SharedPreferencesHandler.USE_LRU_CACHE) as Preference?)?.onPreferenceChangeListener = useLruChangedListener
|
||||||
(findPreference(SharedPreferencesHandler.LRU_CACHE_SIZE) 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") {
|
if (BuildConfig.FLAVOR == "apkstore") {
|
||||||
(findPreference(UPDATE_CHECK_ITEM_KEY) as Preference?)?.onPreferenceClickListener = updateCheckClickListener
|
(findPreference(UPDATE_CHECK_ITEM_KEY) as Preference?)?.onPreferenceClickListener = updateCheckClickListener
|
||||||
}
|
}
|
||||||
@ -263,14 +270,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as SwitchPreferenceCompat?)?.isChecked = enabled
|
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as SwitchPreferenceCompat?)?.isChecked = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rootView(): View {
|
|
||||||
return activity().findViewById(R.id.activityRootView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableAutoUpload() {
|
fun disableAutoUpload() {
|
||||||
onUseAutoPhotoUploadChanged(false)
|
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 {
|
companion object {
|
||||||
|
|
||||||
private const val APP_VERSION_ITEM_KEY = "appVersion"
|
private const val APP_VERSION_ITEM_KEY = "appVersion"
|
||||||
|
@ -20,8 +20,6 @@ import java.io.InvalidClassException
|
|||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashSet
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -114,6 +112,29 @@ class FileUtil @Inject constructor(private val context: Context, private val mim
|
|||||||
return FileInfo(name, mimeTypes)
|
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? {
|
fun storeImagePreviewFiles(imagePreviewFilesStore: ImagePreviewFilesStore?): String? {
|
||||||
decryptedFileStorage.mkdir()
|
decryptedFileStorage.mkdir()
|
||||||
val file = File(decryptedFileStorage, IMAGE_PREVIEW__FILE_NAMES)
|
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>30</item>
|
||||||
<item>Never</item>
|
<item>Never</item>
|
||||||
</string-array>
|
</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>
|
</resources>
|
||||||
|
@ -275,6 +275,8 @@
|
|||||||
<string name="screen_settings_section_version">Version</string>
|
<string name="screen_settings_section_version">Version</string>
|
||||||
|
|
||||||
<string name="screen_settings_advanced_settings">Advanced Settings</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">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_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>
|
<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_positive_button">Enable</string>
|
||||||
<string name="dialog_debug_mode_negative_button" translatable="false">@string/dialog_button_cancel</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_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_disclaimer_title">Attention</string>
|
||||||
<string name="dialog_disable_app_obscured_positive_button">Disable</string>
|
<string name="dialog_disable_app_obscured_positive_button">Disable</string>
|
||||||
|
@ -181,6 +181,12 @@
|
|||||||
|
|
||||||
<PreferenceCategory android:title="@string/screen_settings_advanced_settings">
|
<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
|
<androidx.preference.SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="backgroundUnlockPreparation"
|
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 {
|
companion object {
|
||||||
|
|
||||||
private const val SCREEN_LOCK_DIALOG_SHOWN = "askForScreenLockDialogShown"
|
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 PHOTO_UPLOAD_INCLUDING_VIDEOS = "photoUploadIncludingVideos"
|
||||||
const val USE_LRU_CACHE = "lruCache"
|
const val USE_LRU_CACHE = "lruCache"
|
||||||
const val LRU_CACHE_SIZE = "lruCacheSize"
|
const val LRU_CACHE_SIZE = "lruCacheSize"
|
||||||
|
const val MICROSOFT_WORKAROUND = "shareOfficeFilePublicly"
|
||||||
const val MAIL = "mail"
|
const val MAIL = "mail"
|
||||||
const val UPDATE_INTERVAL = "updateInterval"
|
const val UPDATE_INTERVAL = "updateInterval"
|
||||||
private const val LAST_UPDATE_CHECK = "lastUpdateCheck"
|
private const val LAST_UPDATE_CHECK = "lastUpdateCheck"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user