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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import android.provider.DocumentsContract
import android.provider.DocumentsProvider
import android.provider.Settings
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import com.infomaniak.core.common.cancellable
Expand Down Expand Up @@ -89,6 +90,10 @@ class CloudStorageProvider : DocumentsProvider() {
private val cloudScope = CoroutineScope(
CoroutineName("CloudStorage") + Executors.newSingleThreadExecutor().asCoroutineDispatcher()
)
private fun isProviderDisabled(): Boolean {
val ctx = context ?: return true
return isDisabled(ctx)
}
Comment thread
aymericmariaux marked this conversation as resolved.

/**
* Indicates whether the current platform is Chrome OS.
Expand Down Expand Up @@ -176,6 +181,11 @@ class CloudStorageProvider : DocumentsProvider() {
override fun queryChildDocuments(parentDocumentId: String, projection: Array<out String>?, sortOrder: String?): Cursor {
val cursor = DocumentCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION, isAutoCloseableJob = false)

if (isProviderDisabled()) {
cursor.extras = bundleOf(DocumentsContract.EXTRA_ERROR to (context?.getString(R.string.fileProviderExtensionError)))
return cursor
}
Comment thread
aymericmariaux marked this conversation as resolved.

val uri = DocumentCursor.createUri(context, parentDocumentId)
val isNewJob = uri != oldQueryChildUri || needRefresh

Expand Down Expand Up @@ -285,6 +295,9 @@ class CloudStorageProvider : DocumentsProvider() {

override fun openDocument(documentId: String, mode: String, signal: CancellationSignal?): ParcelFileDescriptor? {
SentryLog.d(TAG, "openDocument(), id=$documentId, mode=$mode, signalIsCancelled: ${signal?.isCanceled}")
if (isProviderDisabled()) {
throw SecurityException(context?.getString(R.string.fileProviderExtensionError))
}
Comment thread
aymericmariaux marked this conversation as resolved.
val context = context ?: return null
Comment on lines 296 to 301
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

When the provider is disabled by a user preference, throwing SecurityException can lead to harsher client behavior than necessary (some clients treat it as an authorization failure). Consider throwing FileNotFoundException (or an appropriate DocumentsContract-aligned exception) with the same message to better reflect 'content unavailable' rather than 'permission denied'.

Copilot uses AI. Check for mistakes.

fun getRemoteFile(localFile: File?, fileId: Int, driveId: Int): File? {
Expand Down Expand Up @@ -863,6 +876,8 @@ class CloudStorageProvider : DocumentsProvider() {
private const val DRIVE_SEPARATOR = "@"
private const val MY_SHARES_FOLDER_ID = -1
private const val SHARED_WITHME_FOLDER_ID = -2
private const val PREFS_NAME = "cloud_storage_provider"
private const val KEY_PROVIDER_DISABLED = "provider_disabled"

private val SHARED_URI_REGEX = Regex("\\d+/-\\d+/.+$DRIVE_SEPARATOR\\d+/\\d+")

Expand Down Expand Up @@ -964,6 +979,15 @@ class CloudStorageProvider : DocumentsProvider() {
comeFromSharedWithMe(documentId)
)

fun isDisabled(context: Context): Boolean {
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).getBoolean(KEY_PROVIDER_DISABLED, false)
}

fun setDisabled(context: Context, disabled: Boolean) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit { putBoolean(KEY_PROVIDER_DISABLED, disabled) }
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

SharedPreferences.edit { ... } uses apply() by default (async). Calling notifyRootsChanged(context) immediately after can race with persistence, so the provider may be re-queried before the new flag is stored. Make the write synchronous (e.g., commit=true / commit()) or otherwise ensure the preference is persisted before notifying roots changed.

Suggested change
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit { putBoolean(KEY_PROVIDER_DISABLED, disabled) }
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit(commit = true) {
putBoolean(KEY_PROVIDER_DISABLED, disabled)
}

Copilot uses AI. Check for mistakes.
notifyRootsChanged(context)
}

fun notifyRootsChanged(context: Context) {
val authority = context.getString(R.string.CLOUD_STORAGE_AUTHORITY)
val rootsUri = DocumentsContract.buildRootsUri(authority)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import com.infomaniak.core.applock.AppLockManager
import com.infomaniak.core.twofactorauth.front.TwoFactorAuthApprovalAutoManagedBottomSheet
import com.infomaniak.core.twofactorauth.front.addComposeOverlay
import com.infomaniak.drive.R
import com.infomaniak.drive.data.documentprovider.CloudStorageProvider
import com.infomaniak.drive.data.models.AppSettings
import com.infomaniak.drive.databinding.ViewSwitchSettingsBinding
import com.infomaniak.drive.twoFactorAuthManager
import splitties.init.appCtx

class AppSecuritySettingsActivity : AppCompatActivity() {

Expand All @@ -54,7 +56,10 @@ class AppSecuritySettingsActivity : AppCompatActivity() {
// Reverse switch (before official parameter changed) by silent click
silentlyReverseSwitch(this) { shouldLock ->
AppSettings.appSecurityLock = shouldLock
if (shouldLock) AppLockManager.unlock()
if (shouldLock) {
CloudStorageProvider.setDisabled(appCtx, true)
AppLockManager.unlock()
}
Comment thread
aymericmariaux marked this conversation as resolved.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.drive.ui.menu.settings

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.infomaniak.core.applock.AppLockManager
import com.infomaniak.core.fragmentnavigation.safelyNavigate
import com.infomaniak.core.legacy.utils.safeBinding
import com.infomaniak.drive.MatomoDrive.MatomoName
import com.infomaniak.drive.MatomoDrive.trackSettingsEvent
import com.infomaniak.drive.R
import com.infomaniak.drive.data.documentprovider.CloudStorageProvider
import com.infomaniak.drive.data.models.AppSettings
import com.infomaniak.drive.databinding.FragmentSettingsSecurityBinding
import com.infomaniak.drive.extensions.enableEdgeToEdge
import splitties.init.appCtx

class SecuritySettingsFragment : Fragment() {

private var binding: FragmentSettingsSecurityBinding by safeBinding()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return FragmentSettingsSecurityBinding.inflate(inflater, container, false).also { binding = it }.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
super.onViewCreated(view, savedInstanceState)

toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}

appSecurity.apply {
if (AppLockManager.hasBiometrics()) {
isVisible = true
setOnClickListener {
trackSettingsEvent(MatomoName.LockApp)
safelyNavigate(R.id.appSecurityActivity)
}
} else {
isGone = true
}
}
contentProviderSwitch.isChecked = !CloudStorageProvider.isDisabled(appCtx)

contentProviderSwitch.setOnCheckedChangeListener { _, isChecked ->
CloudStorageProvider.setDisabled(appCtx, disabled = !isChecked)
}
Comment thread
aymericmariaux marked this conversation as resolved.
Comment on lines +66 to +70
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

In onResume(), assigning contentProviderSwitch.isChecked may trigger the existing setOnCheckedChangeListener, causing redundant preference writes and repeated notifyRootsChanged() calls. A common fix is to temporarily clear/reinstall the listener around the assignment, or only assign when the value actually changes.

Copilot uses AI. Check for mistakes.

binding.root.enableEdgeToEdge()
}

override fun onResume() = with(binding) {
super.onResume()
appSecurity.endText = getString(if (AppSettings.appSecurityLock) R.string.allActivated else R.string.allDisabled)
val isContentProviderEnabled = !CloudStorageProvider.isDisabled(appCtx)
if (contentProviderSwitch.isChecked != isContentProviderEnabled) {
contentProviderSwitch.isChecked = isContentProviderEnabled
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arrayMapOf
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.infomaniak.core.applock.AppLockManager
import com.infomaniak.core.auth.room.UserDatabase
import com.infomaniak.core.bugtracker.BugTrackerActivity
import com.infomaniak.core.bugtracker.BugTrackerActivityArgs
Expand Down Expand Up @@ -93,17 +91,7 @@ class SettingsFragment : Fragment() {
syncPicture.setOnClickListener { safelyNavigate(R.id.syncSettingsActivity) }
themeSettings.setOnClickListener { openThemeSettings() }
notifications.setOnClickListener { requireContext().openAppNotificationSettings() }
appSecurity.apply {
if (AppLockManager.hasBiometrics()) {
isVisible = true
setOnClickListener {
trackSettingsEvent(MatomoName.LockApp)
safelyNavigate(R.id.appSecurityActivity)
}
} else {
isGone = true
}
}
security.setOnClickListener { safelyNavigate(R.id.securitySettingsFragment) }

initFileSync()
about.setOnClickListener { safelyNavigate(R.id.aboutSettingsFragment) }
Expand Down Expand Up @@ -215,7 +203,6 @@ class SettingsFragment : Fragment() {
override fun onResume() = with(binding) {
super.onResume()
syncPicture.endText = getString(if (AccountUtils.isEnableAppSync()) R.string.allActivated else R.string.allDisabled)
appSecurity.endText = getString(if (AppSettings.appSecurityLock) R.string.allActivated else R.string.allDisabled)
setThemeSettingsValue()
}

Expand Down
7 changes: 3 additions & 4 deletions app/src/main/res/layout/fragment_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,11 @@
app:title="@string/notificationTitle" />

<com.infomaniak.drive.ui.menu.settings.ItemSettingView
android:id="@+id/appSecurity"
android:id="@+id/security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endText="@string/allDisabled"
app:itemAction="text"
app:title="@string/appSecurityTitle" />
app:itemAction="chevron"
app:title="@string/securityTitle" />

<com.infomaniak.drive.ui.menu.settings.ItemSettingView
android:id="@+id/fileSync"
Expand Down
83 changes: 83 additions & 0 deletions app/src/main/res/layout/fragment_settings_security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Infomaniak kDrive - Android
~ Copyright (C) 2026 Infomaniak Network SA
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="@dimen/appBarHeight"
android:touchscreenBlocksFocus="false">

<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:title="@string/securityTitle">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:layout_collapseMode="pin" />

</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>

<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<com.google.android.material.card.MaterialCardView
style="@style/CardViewInfomaniak"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/marginStandard">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/divider"
android:orientation="vertical"
android:showDividers="middle">

<com.infomaniak.drive.ui.menu.settings.ItemSettingView
android:id="@+id/appSecurity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endText="@string/allDisabled"
app:itemAction="text"
app:title="@string/appSecurityTitle" />

<com.infomaniak.drive.ui.menu.settings.ItemSettingView
android:id="@+id/contentProviderSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemAction="toggle"
app:title="@string/fileProviderExtensionTitle"
app:description="@string/fileProviderExtensionDescription" />

</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
10 changes: 10 additions & 0 deletions app/src/main/res/navigation/main_navigation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,9 @@
<action
android:id="@+id/action_settingsFragment_to_syncOfflineSelectBottomSheetDialog"
app:destination="@id/syncOfflineSelectBottomSheetDialog" />
<action
android:id="@+id/action_settingsFragment_to_securitySettingsFragment"
app:destination="@id/securitySettingsFragment" />
</fragment>

<activity
Expand All @@ -562,6 +565,13 @@
android:label="AppSecurityActivity"
tools:layout="@layout/view_switch_settings" />

<fragment
android:id="@+id/securitySettingsFragment"
android:name="com.infomaniak.drive.ui.menu.settings.SecuritySettingsFragment"
android:label="SecuritySettingsFragment"
tools:layout="@layout/fragment_settings_security">
</fragment>

<fragment
android:id="@+id/aboutSettingsFragment"
android:name="com.infomaniak.drive.ui.menu.settings.AboutSettingsFragment"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-da/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@
</plurals>
<string name="fileListTitle">Filer</string>
<string name="filePermissionTitle">Vælg rettighed</string>
<string name="fileProviderExtensionDescription">Hvis denne indstilling deaktiveres, vil kDrive ikke længere være tilgængelig i Filer-applikationen og i tredjepartsapplikationer.</string>
<string name="fileProviderExtensionError">Du har deaktiveret visning af filer fra kDrive. Du kan aktivere det igen i appens indstillinger.</string>
<string name="fileProviderExtensionTitle">Vis i Filer</string>
<string name="fileShareAddMessage">Tilføj en besked til gæster (valgfrit)</string>
<string name="fileShareDetailsFileTitle">Deling og rettigheder for fil %s</string>
<string name="fileShareDetailsFolderTitle">Deling og rettigheder for mappe %s</string>
Expand Down Expand Up @@ -597,6 +600,7 @@
<string name="searchNoFile">Ingen resultater, tilpas dine søgekriterier</string>
<string name="searchTitle">Søg</string>
<string name="searchViewHint">Søg efter en fil …</string>
<string name="securityTitle">Sikkerhed</string>
<string name="selectDriveTitle">Vælg en kDrive</string>
<string name="selectFolderTitle">Vælg en mappe</string>
<string name="selectMediaFoldersDescription">Vælg mapperne på din enhed, der skal gemmes automatisk i din kDrive.</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@
</plurals>
<string name="fileListTitle">Dateien</string>
<string name="filePermissionTitle">Recht auswählen</string>
<string name="fileProviderExtensionDescription">Wenn diese Option deaktiviert ist, ist kDrive in der Anwendung Dateien und in Anwendungen von Drittanbietern nicht mehr verfügbar.</string>
<string name="fileProviderExtensionError">Sie haben die Dateianzeige aus kDrive deaktiviert. Sie können sie in den App-Einstellungen wieder aktivieren.</string>
<string name="fileProviderExtensionTitle">Ansicht in Dateien</string>
<string name="fileShareAddMessage">Nachricht an Gäste hinzufügen (fakultativ)</string>
<string name="fileShareDetailsFileTitle">Freigabe und Rechte der Datei %s</string>
<string name="fileShareDetailsFolderTitle">Freigabe und Rechte des Ordners %s</string>
Expand Down Expand Up @@ -597,6 +600,7 @@
<string name="searchNoFile">Kein Ergebnis, bitte ändern Sie Ihre Suchkriterien</string>
<string name="searchTitle">Suchen</string>
<string name="searchViewHint">Datei suchen…</string>
<string name="securityTitle">Sicherheit</string>
<string name="selectDriveTitle">kDrive auswählen</string>
<string name="selectFolderTitle">Ordner auswählen</string>
<string name="selectMediaFoldersDescription">Wählen Sie die Ordner auf Ihrem Gerät aus, die automatisch auf Ihrem kDrive gesichert werden sollen.</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-el/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@
</plurals>
<string name="fileListTitle">Αρχεία</string>
<string name="filePermissionTitle">Επιλέξτε το δικαίωμα</string>
<string name="fileProviderExtensionDescription">Εάν αυτή η επιλογή απενεργοποιηθεί, το kDrive δεν θα είναι πλέον διαθέσιμο στην εφαρμογή Αρχεία και σε εφαρμογές τρίτων.</string>
<string name="fileProviderExtensionError">Έχετε απενεργοποιήσει την εμφάνιση αρχείων από το kDrive. Μπορείτε να την ενεργοποιήσετε ξανά στις ρυθμίσεις της εφαρμογής.</string>
<string name="fileProviderExtensionTitle">Εμφάνιση στα Αρχεία</string>
<string name="fileShareAddMessage">Προσθήκη μηνύματος για επισκέπτες (προαιρετικό)</string>
<string name="fileShareDetailsFileTitle">Κοινή χρήση και δικαιώματα του αρχείου %s</string>
<string name="fileShareDetailsFolderTitle">Κοινή χρήση και δικαιώματα του φακέλου %s</string>
Expand Down Expand Up @@ -597,6 +600,7 @@
<string name="searchNoFile">Χωρίς αποτελέσματα, τροποποιήστε τα κριτήρια αναζήτησης</string>
<string name="searchTitle">Αναζήτηση</string>
<string name="searchViewHint">Αναζήτηση αρχείου…</string>
<string name="securityTitle">Ασφάλεια</string>
<string name="selectDriveTitle">Επιλέξτε ένα kDrive</string>
<string name="selectFolderTitle">Επιλέξτε έναν φάκελο</string>
<string name="selectMediaFoldersDescription">Επιλέξτε τους φακέλους στη συσκευή σας που θα πρέπει να αποθηκευτούν αυτόματα στο kDrive σας.</string>
Expand Down
Loading
Loading