Skip to content
Merged
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
20 changes: 0 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,6 @@ The app is intentionally kept very basic so that the project is easy to maintain
* `INTERACT_ACROSS_USERS` (Android >=17)
* Optionally used to allow two separate BasicSync instances installed in different Android profiles or users to talk to each other over localhost. Can only be granted via `adb`. See the [cross-user communication](#cross-user-communication) section for more details.

## Other apps losing permissions

With Android's September 2026 security patches, other apps will lose permissions to shared files when they are synced by default.

Syncthing normally overwrites files safely during syncing by:

1. Writing the new data to a temporary file (`.syncthing.<name>.tmp`)
2. Deleting the original file
3. Renaming the temporary file to the original name

However, with Android's September 2026 security patches, renaming or deleting files (steps 2 and 3) will cause other apps to lose permissions to them.

Turning off the "Overwrite files safely" option on BasicSync's main screen will work around this problem. This forces Syncthing to always use its fallback mechanism for overwriting files by:

1. Writing the new data to a temporary file (`.syncthing.<name>.tmp`)
2. Overwriting the original file in place, copying data from the temporary file
3. Deleting the temporary file

Android will not revoke other apps' permissions in this case because the original file is never renamed or deleted. Turning off safe overwrites can potentially cause issues in the event that the app crashes during step 2 where the original file is overwritten in place. The partially overwritten file will create a sync conflict the next time the app starts up that must be manually resolved.

## Remote web UI access

Syncthing listens on the loopback interface and is available via `127.0.0.1:8384` by default. BasicSync will try to use the same port on every start, but will automatically pick a new random port if there is a conflict. The current port number can be found in Web UI -> Actions -> Settings -> GUI. HTTPS and basic authentication are both forcibly enabled every time Syncthing starts.
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
android:enableOnBackInvokedCallback="true"
android:memtagMode="sync"
android:requestLegacyExternalStorage="true"
android:requestRawExternalStorageAccess="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicSync"
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/java/com/chiller3/basicsync/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ class MainApplication : Application() {

Notifications(this).updateChannels()

val prefs = Preferences(this)
prefs.migrate()
Preferences(this).migrate()

Stbridge.initDirs(
filesDir.toString(),
cacheDir.toString(),
getExternalFilesDir(null)!!.toString(),
)
Stbridge.setSafClient(SyncthingSafClient(this))
Stbridge.setAllowSafeOverwrites(prefs.allowSafeOverwrites)
}
}
10 changes: 5 additions & 5 deletions app/src/main/java/com/chiller3/basicsync/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class Preferences(context: Context) {
const val PREF_REMOTE_CONTROL = "remote_control"
const val PREF_ALLOW_AUTO_MODE = "allow_auto_mode"
const val PREF_START_ON_BOOT = "start_on_boot"
const val PREF_ALLOW_SAFE_OVERWRITES = "allow_safe_overwrites"
const val PREF_REQUIRE_UNMETERED_NETWORK = "require_unmetered_network"
const val PREF_NETWORK_ALLOW_WIFI = "network_allow_wifi"
const val PREF_NETWORK_ALLOW_CELLULAR = "network_allow_cellular"
Expand All @@ -45,6 +44,7 @@ class Preferences(context: Context) {

// Legacy preferences.
private const val PREF_REQUIRE_SUFFICIENT_BATTERY = "require_sufficient_battery"
private const val PREF_ALLOW_SAFE_OVERWRITES = "allow_safe_overwrites"
}

private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
Expand Down Expand Up @@ -97,10 +97,6 @@ class Preferences(context: Context) {
get() = prefs.getBoolean(PREF_START_ON_BOOT, true)
set(enabled) = prefs.edit { putBoolean(PREF_START_ON_BOOT, enabled) }

var allowSafeOverwrites: Boolean
get() = prefs.getBoolean(PREF_ALLOW_SAFE_OVERWRITES, true)
set(enabled) = prefs.edit { putBoolean(PREF_ALLOW_SAFE_OVERWRITES, enabled) }

var requireUnmeteredNetwork: Boolean
get() = prefs.getBoolean(PREF_REQUIRE_UNMETERED_NETWORK, true)
set(enabled) = prefs.edit { putBoolean(PREF_REQUIRE_UNMETERED_NETWORK, enabled) }
Expand Down Expand Up @@ -174,6 +170,10 @@ class Preferences(context: Context) {
prefs.edit { remove(PREF_REQUIRE_SUFFICIENT_BATTERY) }
}

if (prefs.contains(PREF_ALLOW_SAFE_OVERWRITES)) {
prefs.edit { remove(PREF_ALLOW_SAFE_OVERWRITES) }
}

clampSyncScheduleDurations(true)
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ fun SettingsScreen(
val remoteControl = remember(reloadPrefs) { prefs.remoteControl }
val allowAutoMode = remember(reloadPrefs) { prefs.allowAutoMode }
val startOnBoot = remember(reloadPrefs) { prefs.startOnBoot }
val allowSafeOverwrites = remember(reloadPrefs) { prefs.allowSafeOverwrites }
val isDebugMode = remember(reloadPrefs) { prefs.isDebugMode }

var reloadPerms by remember { mutableIntStateOf(0) }
Expand Down Expand Up @@ -337,7 +336,6 @@ fun SettingsScreen(
remoteControl = remoteControl,
allowAutoMode = allowAutoMode,
startOnBoot = startOnBoot,
allowSafeOverwrites = allowSafeOverwrites,
isDebugMode = isDebugMode,
onInhibitBatteryOptGrant = {
requestInhibitBatteryOpt.launch(Permissions.getInhibitBatteryOptIntent(context))
Expand Down Expand Up @@ -472,11 +470,6 @@ fun SettingsScreen(
prefs.startOnBoot = enabled
reloadPrefs++
},
onAllowSafeOverwritesChange = { enabled ->
// SyncthingService watches this and applies the change to stbridge.
prefs.allowSafeOverwrites = enabled
reloadPrefs++
},
onDebugModeChange = { enabled ->
prefs.isDebugMode = enabled
reloadPrefs++
Expand Down Expand Up @@ -584,7 +577,6 @@ private fun SettingsContent(
remoteControl: Boolean,
allowAutoMode: Boolean,
startOnBoot: Boolean,
allowSafeOverwrites: Boolean,
isDebugMode: Boolean,
onInhibitBatteryOptGrant: () -> Unit,
onNotificationsGrant: () -> Unit,
Expand All @@ -610,7 +602,6 @@ private fun SettingsContent(
onRemoteControlChange: (Boolean) -> Unit,
onAllowAutoModeChange: (Boolean) -> Unit,
onStartOnBootChange: (Boolean) -> Unit,
onAllowSafeOverwritesChange: (Boolean) -> Unit,
onDebugModeChange: (Boolean) -> Unit,
onSourceRepoOpen: () -> Unit,
onSaveLogs: () -> Unit,
Expand Down Expand Up @@ -677,7 +668,6 @@ private fun SettingsContent(
val runState = serviceState?.runState

var showMinBatteryLevelDialog by rememberSaveable { mutableStateOf(false) }
var showAllowSafeOverwritesDialog by rememberSaveable { mutableStateOf(false) }

PreferenceColumn(contentPadding = contentPadding) {
if (missingPermissions.isNotEmpty()) {
Expand Down Expand Up @@ -924,25 +914,13 @@ private fun SettingsContent(
SwitchPreference(
checked = startOnBoot,
onCheckedChange = onStartOnBootChange,
shapes = BetterSegmentedShapes.middle(),
shapes = BetterSegmentedShapes.bottom(),
title = { Text(text = stringResource(R.string.pref_start_on_boot_name)) },
summary = { Text(text = stringResource(R.string.pref_start_on_boot_desc)) },
modifier = Modifier.animateItem(),
)
}

item(key = "allow_safe_overwrites") {
SplitSwitchPreference(
onClick = { showAllowSafeOverwritesDialog = true },
checked = allowSafeOverwrites,
onCheckedChange = onAllowSafeOverwritesChange,
shapes = BetterSegmentedShapes.bottom(),
title = { Text(text = stringResource(R.string.pref_allow_safe_overwrites_name)) },
summary = { Text(text = stringResource(R.string.pref_allow_safe_overwrites_desc)) },
modifier = Modifier.animateItem(),
)
}

item(key = "about") {
PreferenceCategory(
title = { Text(text = stringResource(R.string.pref_header_about)) },
Expand Down Expand Up @@ -993,14 +971,6 @@ private fun SettingsContent(
},
)
}

if (showAllowSafeOverwritesDialog) {
AllowSafeOverwritesDialog(
onDismiss = {
showAllowSafeOverwritesDialog = false
},
)
}
}

@Composable
Expand Down Expand Up @@ -1104,7 +1074,6 @@ private fun PreviewSettingsScreen() {
remoteControl = false,
allowAutoMode = true,
startOnBoot = true,
allowSafeOverwrites = true,
isDebugMode = true,
onInhibitBatteryOptGrant = {},
onNotificationsGrant = {},
Expand All @@ -1130,7 +1099,6 @@ private fun PreviewSettingsScreen() {
onRemoteControlChange = {},
onAllowAutoModeChange = {},
onStartOnBootChange = {},
onAllowSafeOverwritesChange = {},
onDebugModeChange = {},
onSourceRepoOpen = {},
onSaveLogs = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ServiceInfo
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Binder
import android.os.Build
Expand Down Expand Up @@ -427,6 +428,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener

private lateinit var prefs: Preferences
private lateinit var notifications: Notifications
private val mediaScanner = MediaScannerConnection(this, null)
private val runnerThread = Thread(::runner)

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
Expand Down Expand Up @@ -541,6 +543,8 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
deviceStateTracker = DeviceStateTracker(this)
deviceStateTracker.registerListener(this)

mediaScanner.connect()

runnerThread.start()
}

Expand All @@ -565,6 +569,8 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener

deviceStateTracker.unregisterListener(this)

mediaScanner.disconnect()

Log.d(TAG, "Exiting")
}

Expand Down Expand Up @@ -634,10 +640,6 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
when (key) {
in BLOCKED_REASONS_PREFS, in DeviceState.PREFS -> recomputeBlockedReasons = true
in STATE_CHANGE_PREFS -> {}
Preferences.PREF_ALLOW_SAFE_OVERWRITES -> {
Stbridge.setAllowSafeOverwrites(prefs.allowSafeOverwrites)
return
}
else -> return
}

Expand Down Expand Up @@ -971,6 +973,16 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
}
}

override fun onRemoteFileUpdated(path: String, isDelete: Boolean) {
try {
mediaScanner.scanFile(path, null)
} catch (e: IllegalStateException) {
// On exit, we don't wait for the runner thread to stop. There's a small chance we could
// still receive an event after disconnection.
Log.w(TAG, "Received remote file update event after shutdown", e)
}
}

@WorkerThread
override fun onAlertsUpdated(count: Int) {
synchronized(stateLock) {
Expand Down
3 changes: 0 additions & 3 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,4 @@
<string name="pref_show_details_name">Detaillierte Benachrichtigungen</string>
<string name="pref_show_details_desc">Den Status von freigegebenen Ordnern und verbundenen Geräten in der dauerhaften Benachrichtigung anzeigen.</string>
<string name="pref_network_allow_roaming_name">Mobile Daten erlauben (Roaming)</string>
<string name="pref_allow_safe_overwrites_name">Dateien sicher überschreiben</string>
<string name="pref_allow_safe_overwrites_desc">Mit den Android-Sicherheitspatches vom September 2026 verhindert das Deaktivieren dieser Funktion, dass andere Apps bei der Synchronisierung die Berechtigungen für freigegebene Dateien verlieren.</string>
<string name="pref_allow_safe_overwrites_info">Standardmäßig überschreibt Syncthing Dateien auf sichere Weise, indem es die neuen Daten zunächst in eine temporäre Datei schreibt, die alte Datei löscht und anschließend die temporäre Datei umbenennt. Mit den Sicherheitspatches von Android vom September 2026 führen diese Dateioperationen jedoch dazu, dass andere Apps bei jeder Synchronisierung ihre Zugriffsrechte auf freigegebene Dateien verlieren.\n\nWenn das sichere Überschreiben deaktiviert ist, schreibt Syncthing die neuen Daten in eine temporäre Datei, überschreibt die Originaldatei direkt an Ort und Stelle und löscht anschließend die temporäre Datei. Dies führt vor allem dann zu Problemen, wenn die App während der Synchronisierung abstürzt. Die teilweise überschriebene Datei verursacht einen Synchronisierungskonflikt, der manuell behoben werden muss.</string>
</resources>
3 changes: 0 additions & 3 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,4 @@
<string name="pref_show_details_desc">Afficher l\'état des partages et appareils dans la notification persistante.</string>
<string name="pref_show_details_name">Notifications détaillées</string>
<string name="pref_network_allow_roaming_name">Autoriser (en itinérance) sur les données mobiles</string>
<string name="pref_allow_safe_overwrites_name">Écraser les fichiers de façon sécurisée</string>
<string name="pref_allow_safe_overwrites_desc">Avec les correctifs de sécurité Android de septembre 2026, la désactivation de cette option empêche les autres applications de perdre leurs autorisations d\'accès aux fichiers partagés lors de leur synchronisation.</string>
<string name="pref_allow_safe_overwrites_info">Par défaut, Syncthing écrase les fichiers de façon sécurisée en écrivant d’abord les nouvelles données dans un fichier temporaire, supprimant l’ancien fichier, puis en renommant le fichier temporaire. Cependant, avec les correctifs de sécurité d’Android de septembre 2026, ces opérations sur les fichiers entraîneront la perte des autorisations d’accès aux fichiers partagés pour les autres applications à chaque synchronisation.\n\nSi le remplacement sécurisé est désactivé, Syncthing écrira les nouvelles données dans un fichier temporaire, remplacera le fichier d\'origine sur place, puis supprimera le fichier temporaire. Cela pose principalement des problèmes si l\'application plante pendant la synchronisation. Le fichier partiellement remplacé créera un conflit de synchronisation qui devra être résolu manuellement.</string>
</resources>
3 changes: 0 additions & 3 deletions app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,4 @@
<string name="pref_header_notifications">Powiadomienia</string>
<string name="pref_show_details_name">Powiadomienia szczegółowe</string>
<string name="pref_network_allow_roaming_name">Zezwól na dane mobilne (roaming)</string>
<string name="pref_allow_safe_overwrites_name">Bezpiecznie nadpisz pliki</string>
<string name="pref_allow_safe_overwrites_desc">W przypadku wrześniowych poprawek bezpieczeństwa systemu Android z 2026 roku wyłączenie tej opcji zapobiega utracie przez inne aplikacje uprawnień do plików współdzielonych podczas ich synchronizacji.</string>
<string name="pref_allow_safe_overwrites_info">Domyślnie Syncthing nadpisuje pliki w bezpieczny sposób: najpierw zapisuje nowe dane w pliku tymczasowym, usuwa stary plik, a następnie zmienia nazwę pliku tymczasowego. Jednak w związku z wrześniowymi poprawkami bezpieczeństwa systemu Android z 2026 roku, operacje te będą powodować utratę uprawnień innych aplikacji do współdzielonych plików przy każdej synchronizacji.\n\nJeśli bezpieczne nadpisywanie zostanie wyłączone, Syncthing zapisze nowe dane w pliku tymczasowym, nadpisze oryginalny plik w jego bieżącej lokalizacji, a następnie usunie plik tymczasowy. Problemy mogą wystąpić głównie w przypadku awarii aplikacji podczas synchronizacji. Częściowo nadpisany plik spowoduje konflikt synchronizacji, który trzeba będzie rozwiązać ręcznie.</string>
</resources>
3 changes: 0 additions & 3 deletions app/src/main/res/values-pt-rBR/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,4 @@
<item quantity="many">%d horas</item>
<item quantity="other">%d horas</item>
</plurals>
<string name="pref_allow_safe_overwrites_name">Substituir arquivos com segurança</string>
<string name="pref_allow_safe_overwrites_desc">Com os patches de segurança de setembro de 2026 do Android, desligar isso evita que outros aplicativos percam permissões para arquivos compartilhados quando eles são sincronizados.</string>
<string name="pref_allow_safe_overwrites_info">Por padrão, o Syncthing substitui os arquivos com segurança, gravando primeiro os novos dados em um arquivo temporário, excluindo o arquivo antigo e, em seguida, renomeando o arquivo temporário. No entanto, com os patches de segurança de setembro de 2026 do Android, essas operações de arquivo farão com que outros aplicativos percam suas permissões para arquivos compartilhados sempre que forem sincronizados.\n\nSe as substituições seguras estiverem desativadas, o Syncthing gravará os novos dados em um arquivo temporário, substituirá o arquivo original e, em seguida, excluirá o arquivo temporário. Isso causa problemas principalmente se o aplicativo travar durante a sincronização. O arquivo parcialmente substituído criará um conflito de sincronização que deverá ser resolvido manualmente.</string>
</resources>
Loading
Loading