Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions opencloudApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
API >= 23; the app needs to handle this
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<!--
Notifications are off by default since API 33;
See note in https://developer.android.com/develop/ui/views/notifications/notification-permission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import eu.opencloud.android.usecases.synchronization.SynchronizeFolderUseCase
import eu.opencloud.android.usecases.transfers.downloads.DownloadFileUseCase
import eu.opencloud.android.usecases.transfers.uploads.UploadFilesFromSystemUseCase
import eu.opencloud.android.utils.FileStorageUtils
import eu.opencloud.android.utils.NotificationUtils

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -158,13 +158,9 @@ class DocumentsStorageProvider : DocumentsProvider() {
)
)
Timber.d("Synced ${ocFile.remotePath} from ${ocFile.owner} with result: $result")
if (result.getDataOrNull() is SynchronizeFileUseCase.SyncType.ConflictDetected) {
context?.let {
NotificationUtils.notifyConflict(
fileInConflict = ocFile,
context = it
)
}
if (result.getDataOrNull() is SynchronizeFileUseCase.SyncType.ConflictResolvedWithCopy) {
val conflictResult = result.getDataOrNull() as SynchronizeFileUseCase.SyncType.ConflictResolvedWithCopy
Timber.i("File sync conflict auto-resolved. Conflicted copy at: ${conflictResult.conflictedCopyPath}")
}
}.start()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import eu.opencloud.android.presentation.authentication.EXTRA_ACCOUNT
import eu.opencloud.android.presentation.authentication.EXTRA_ACTION
import eu.opencloud.android.presentation.authentication.LoginActivity
import eu.opencloud.android.presentation.common.UIResult
import eu.opencloud.android.presentation.conflicts.ConflictsResolveActivity

import eu.opencloud.android.presentation.files.details.FileDetailsViewModel.ActionsInDetailsView.NONE
import eu.opencloud.android.presentation.files.details.FileDetailsViewModel.ActionsInDetailsView.SYNC
import eu.opencloud.android.presentation.files.details.FileDetailsViewModel.ActionsInDetailsView.SYNC_AND_OPEN
Expand Down Expand Up @@ -190,10 +190,8 @@ class FileDetailsFragment : FileFragment() {
SynchronizeFileUseCase.SyncType.AlreadySynchronized -> {
showMessageInSnackbar(getString(R.string.sync_file_nothing_to_do_msg))
}
is SynchronizeFileUseCase.SyncType.ConflictDetected -> {
val showConflictActivityIntent = Intent(requireActivity(), ConflictsResolveActivity::class.java)
showConflictActivityIntent.putExtra(ConflictsResolveActivity.EXTRA_FILE, file)
startActivity(showConflictActivityIntent)
is SynchronizeFileUseCase.SyncType.ConflictResolvedWithCopy -> {
showMessageInSnackbar(getString(R.string.sync_conflict_resolved_with_copy))
}

is SynchronizeFileUseCase.SyncType.DownloadEnqueued -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ import eu.opencloud.android.presentation.security.biometric.BiometricManager
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity
import eu.opencloud.android.presentation.security.pattern.PatternActivity
import eu.opencloud.android.presentation.settings.SettingsFragment.Companion.removePreferenceFromScreen
import eu.opencloud.android.providers.WorkManagerProvider
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel

class SettingsSecurityFragment : PreferenceFragmentCompat() {

// ViewModel
private val securityViewModel by viewModel<SettingsSecurityViewModel>()
private val workManagerProvider: WorkManagerProvider by inject()

private var screenSecurity: PreferenceScreen? = null
private var prefPasscode: CheckBoxPreference? = null
Expand All @@ -56,6 +59,9 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
private var prefLockApplication: ListPreference? = null
private var prefLockAccessDocumentProvider: CheckBoxPreference? = null
private var prefTouchesWithOtherVisibleWindows: CheckBoxPreference? = null
private var prefDownloadEverything: CheckBoxPreference? = null
private var prefAutoSync: CheckBoxPreference? = null
private var prefPreferLocalOnConflict: CheckBoxPreference? = null

private val enablePasscodeLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Expand Down Expand Up @@ -111,6 +117,16 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_security, rootKey)
initializePreferences(rootKey)
configureLockPreferences()
configureBiometricPreference()
configureSecurityPreferences()
configureDownloadAndSyncPreferences()
}


@Suppress("UnusedParameter")
private fun initializePreferences(rootKey: String?) {
screenSecurity = findPreference(SCREEN_SECURITY)
prefPasscode = findPreference(PassCodeActivity.PREFERENCE_SET_PASSCODE)
prefPattern = findPreference(PatternActivity.PREFERENCE_SET_PATTERN)
Expand All @@ -132,10 +148,15 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
}
prefLockAccessDocumentProvider = findPreference(PREFERENCE_LOCK_ACCESS_FROM_DOCUMENT_PROVIDER)
prefTouchesWithOtherVisibleWindows = findPreference(PREFERENCE_TOUCHES_WITH_OTHER_VISIBLE_WINDOWS)
prefDownloadEverything = findPreference(PREFERENCE_DOWNLOAD_EVERYTHING)
prefAutoSync = findPreference(PREFERENCE_AUTO_SYNC)
prefPreferLocalOnConflict = findPreference(PREFERENCE_PREFER_LOCAL_ON_CONFLICT)

prefPasscode?.isVisible = !securityViewModel.isSecurityEnforcedEnabled()
prefPattern?.isVisible = !securityViewModel.isSecurityEnforcedEnabled()
}

private fun configureLockPreferences() {
// Passcode lock
prefPasscode?.setOnPreferenceChangeListener { _: Preference?, newValue: Any ->
if (securityViewModel.isPatternSet()) {
Expand Down Expand Up @@ -169,8 +190,9 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
}
false
}
}

// Biometric lock
private fun configureBiometricPreference() {
if (prefBiometric != null) {
if (!BiometricManager.isHardwareDetected()) { // Biometric not supported
screenSecurity?.removePreferenceFromScreen(prefBiometric)
Expand All @@ -192,8 +214,12 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
}

// Lock application
if (prefPasscode?.isChecked == false && prefPattern?.isChecked == false) { prefLockApplication?.isEnabled = false }
if (prefPasscode?.isChecked == false && prefPattern?.isChecked == false) {
prefLockApplication?.isEnabled = false
}
}

private fun configureSecurityPreferences() {
// Lock access from document provider
prefLockAccessDocumentProvider?.setOnPreferenceChangeListener { _: Preference?, newValue: Any ->
securityViewModel.setPrefLockAccessDocumentProvider(true)
Expand Down Expand Up @@ -224,6 +250,62 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
}
}

private fun configureDownloadAndSyncPreferences() {
// Download Everything Feature
prefDownloadEverything?.setOnPreferenceChangeListener { _: Preference?, newValue: Any ->
if (newValue as Boolean) {
activity?.let {
AlertDialog.Builder(it)
.setTitle(getString(R.string.download_everything_warning_title))
.setMessage(getString(R.string.download_everything_warning_message))
.setNegativeButton(getString(R.string.common_no), null)
.setPositiveButton(getString(R.string.common_yes)) { _, _ ->
securityViewModel.setDownloadEverything(true)
prefDownloadEverything?.isChecked = true
workManagerProvider.enqueueDownloadEverythingWorker()
}
.show()
.avoidScreenshotsIfNeeded()
}
return@setOnPreferenceChangeListener false
} else {
securityViewModel.setDownloadEverything(false)
workManagerProvider.cancelDownloadEverythingWorker()
true
}
}

// Auto-Sync Feature
prefAutoSync?.setOnPreferenceChangeListener { _: Preference?, newValue: Any ->
if (newValue as Boolean) {
activity?.let {
AlertDialog.Builder(it)
.setTitle(getString(R.string.auto_sync_warning_title))
.setMessage(getString(R.string.auto_sync_warning_message))
.setNegativeButton(getString(R.string.common_no), null)
.setPositiveButton(getString(R.string.common_yes)) { _, _ ->
securityViewModel.setAutoSync(true)
prefAutoSync?.isChecked = true
workManagerProvider.enqueueLocalFileSyncWorker()
}
.show()
.avoidScreenshotsIfNeeded()
}
return@setOnPreferenceChangeListener false
} else {
securityViewModel.setAutoSync(false)
workManagerProvider.cancelLocalFileSyncWorker()
true
}
}

// Conflict Resolution Strategy
prefPreferLocalOnConflict?.setOnPreferenceChangeListener { _: Preference?, newValue: Any ->
securityViewModel.setPreferLocalOnConflict(newValue as Boolean)
true
}
}

private fun enableBiometricAndLockApplication() {
prefBiometric?.apply {
isEnabled = true
Expand All @@ -246,5 +328,8 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
const val PREFERENCE_TOUCHES_WITH_OTHER_VISIBLE_WINDOWS = "touches_with_other_visible_windows"
const val EXTRAS_LOCK_ENFORCED = "EXTRAS_LOCK_ENFORCED"
const val PREFERENCE_LOCK_ATTEMPTS = "PrefLockAttempts"
const val PREFERENCE_DOWNLOAD_EVERYTHING = "download_everything"
const val PREFERENCE_AUTO_SYNC = "auto_sync_local_changes"
const val PREFERENCE_PREFER_LOCAL_ON_CONFLICT = "prefer_local_on_conflict"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,25 @@ class SettingsSecurityViewModel(
integerKey = R.integer.lock_delay_enforced
)
) != LockTimeout.DISABLED

// Download Everything Feature
fun isDownloadEverythingEnabled(): Boolean =
preferencesProvider.getBoolean(SettingsSecurityFragment.PREFERENCE_DOWNLOAD_EVERYTHING, false)

fun setDownloadEverything(enabled: Boolean) =
preferencesProvider.putBoolean(SettingsSecurityFragment.PREFERENCE_DOWNLOAD_EVERYTHING, enabled)

// Auto-Sync Feature
fun isAutoSyncEnabled(): Boolean =
preferencesProvider.getBoolean(SettingsSecurityFragment.PREFERENCE_AUTO_SYNC, false)

fun setAutoSync(enabled: Boolean) =
preferencesProvider.putBoolean(SettingsSecurityFragment.PREFERENCE_AUTO_SYNC, enabled)

// Conflict Resolution Strategy
fun isPreferLocalOnConflictEnabled(): Boolean =
preferencesProvider.getBoolean(SettingsSecurityFragment.PREFERENCE_PREFER_LOCAL_ON_CONFLICT, false)

fun setPreferLocalOnConflict(enabled: Boolean) =
preferencesProvider.putBoolean(SettingsSecurityFragment.PREFERENCE_PREFER_LOCAL_ON_CONFLICT, enabled)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import eu.opencloud.android.workers.AccountDiscoveryWorker
import eu.opencloud.android.workers.AvailableOfflinePeriodicWorker
import eu.opencloud.android.workers.AvailableOfflinePeriodicWorker.Companion.AVAILABLE_OFFLINE_PERIODIC_WORKER
import eu.opencloud.android.workers.AutomaticUploadsWorker
import eu.opencloud.android.workers.DownloadEverythingWorker
import eu.opencloud.android.workers.LocalFileSyncWorker
import eu.opencloud.android.workers.OldLogsCollectorWorker
import eu.opencloud.android.workers.RemoveLocallyFilesWithLastUsageOlderThanGivenTimeWorker
import eu.opencloud.android.workers.UploadFileFromContentUriWorker
Expand Down Expand Up @@ -129,4 +131,60 @@ class WorkManagerProvider(

fun cancelAllWorkByTag(tag: String) = WorkManager.getInstance(context).cancelAllWorkByTag(tag)

// Download Everything Feature
fun enqueueDownloadEverythingWorker() {
val constraintsRequired = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.build()

val downloadEverythingWorker = PeriodicWorkRequestBuilder<DownloadEverythingWorker>(
repeatInterval = DownloadEverythingWorker.repeatInterval,
repeatIntervalTimeUnit = DownloadEverythingWorker.repeatIntervalTimeUnit
)
.addTag(DownloadEverythingWorker.DOWNLOAD_EVERYTHING_WORKER)
.setConstraints(constraintsRequired)
.build()

WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
DownloadEverythingWorker.DOWNLOAD_EVERYTHING_WORKER,
ExistingPeriodicWorkPolicy.KEEP,
downloadEverythingWorker
)
}

fun cancelDownloadEverythingWorker() {
WorkManager.getInstance(context)
.cancelUniqueWork(DownloadEverythingWorker.DOWNLOAD_EVERYTHING_WORKER)
}

// Local File Sync (Auto-Sync) Feature
fun enqueueLocalFileSyncWorker() {
val constraintsRequired = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

val localFileSyncWorker = PeriodicWorkRequestBuilder<LocalFileSyncWorker>(
repeatInterval = LocalFileSyncWorker.repeatInterval,
repeatIntervalTimeUnit = LocalFileSyncWorker.repeatIntervalTimeUnit
)
.addTag(LocalFileSyncWorker.LOCAL_FILE_SYNC_WORKER)
.setConstraints(constraintsRequired)
.build()

WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
LocalFileSyncWorker.LOCAL_FILE_SYNC_WORKER,
ExistingPeriodicWorkPolicy.KEEP,
localFileSyncWorker
)
}

fun cancelLocalFileSyncWorker() {
WorkManager.getInstance(context)
.cancelUniqueWork(LocalFileSyncWorker.LOCAL_FILE_SYNC_WORKER)
}

}
Loading
Loading