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 @@ -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 @@ -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 @@ -132,6 +138,9 @@ 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()
Expand Down Expand Up @@ -222,6 +231,60 @@ class SettingsSecurityFragment : PreferenceFragmentCompat() {
}
true
}

// 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() {
Expand All @@ -246,5 +309,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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import eu.opencloud.android.presentation.spaces.SpacesListFragment.Companion.BUN
import eu.opencloud.android.presentation.spaces.SpacesListFragment.Companion.REQUEST_KEY_CLICK_SPACE
import eu.opencloud.android.presentation.spaces.SpacesListViewModel
import eu.opencloud.android.presentation.transfers.TransfersViewModel
import eu.opencloud.android.presentation.settings.security.SettingsSecurityFragment
import eu.opencloud.android.providers.WorkManagerProvider
import eu.opencloud.android.syncadapter.FileSyncAdapter
import eu.opencloud.android.ui.dialog.FileAlreadyExistsDialog
Expand Down Expand Up @@ -284,9 +285,28 @@ class FileDisplayActivity : FileActivity(),


checkNotificationPermission()
checkManageExternalStoragePermission()
Timber.v("onCreate() end")
}

private fun checkManageExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!android.os.Environment.isExternalStorageManager()) {
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.app_name))
builder.setMessage("To save offline files, the app needs access to all files.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strings should probably be translated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, can do micro patch later

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets have focus on implementing the sync stuff perfectly, translation etc can be done later

builder.setPositiveButton("Settings") { _, _ ->
val intent = Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.addCategory("android.intent.category.DEFAULT")
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
builder.setNegativeButton("Cancel", null)
builder.show()
}
}
}

private fun checkNotificationPermission() {
// Ask for permission only in case it's api >= 33 and notifications are not granted.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
Expand Down Expand Up @@ -376,6 +396,16 @@ class FileDisplayActivity : FileActivity(),
syncProfileOperation.syncUserProfile()
val workManagerProvider = WorkManagerProvider(context = baseContext)
workManagerProvider.enqueueAvailableOfflinePeriodicWorker()

// Enqueue Download Everything worker if enabled
if (sharedPreferences.getBoolean(SettingsSecurityFragment.PREFERENCE_DOWNLOAD_EVERYTHING, false)) {
workManagerProvider.enqueueDownloadEverythingWorker()
}

// Enqueue Local File Sync worker if enabled
if (sharedPreferences.getBoolean(SettingsSecurityFragment.PREFERENCE_AUTO_SYNC, false)) {
workManagerProvider.enqueueLocalFileSyncWorker()
}
} else {
file?.isFolder?.let { isFolder ->
updateFragmentsVisibility(!isFolder)
Expand Down Expand Up @@ -1354,10 +1384,8 @@ class FileDisplayActivity : FileActivity(),
}
}

is SynchronizeFileUseCase.SyncType.ConflictDetected -> {
val showConflictActivityIntent = Intent(this, ConflictsResolveActivity::class.java)
showConflictActivityIntent.putExtra(ConflictsResolveActivity.EXTRA_FILE, file)
startActivity(showConflictActivityIntent)
is SynchronizeFileUseCase.SyncType.ConflictResolvedWithCopy -> {
showSnackMessage(getString(R.string.sync_conflict_resolved_with_copy))
}

is SynchronizeFileUseCase.SyncType.DownloadEnqueued -> {
Expand Down
Loading
Loading