Skip to content

Commit 201cbe7

Browse files
authored
feat: settings backup 29 (#160)
* docs: settings backup plan * docs: car mode settings crash fix * docs: backup format * docs: perms * feat: phase 1 and 2 * test: phase 1 and 2 test * feat: UI * feat: draw the rest of the fucking owl lol * fix: narrow exceptions * fix: bug bot Car mode Bluetooth devices excluded from backup High Severity The EXCLUDED_KEYS set containing "A", "B", "C" is applied globally to all preference files, but BTCarModeStorage uses key "A" for carModeTriggerDevicesRaw (the user's configured Bluetooth devices). Since exportSharedPreferences is called for both default and car mode preferences with the same exclusion filter, the car mode Bluetooth device configuration will never be exported. This defeats the purpose of backing up car mode settings and results in data loss for users who rely on this feature. Long preferences become Int causing ClassCastException on import High Severity When importing settings to a fresh install, Long preferences that fit within Int range (like versionCodeFirstInstalled) are incorrectly stored as Int because prefs.all[key] returns null and the code defaults to putInt. The app subsequently calls getLong() to read these values, which throws a ClassCastException when the value is actually stored as Int. This will crash the app after importing a backup to a new device or fresh install. * feat: calendar handled backup * fix: and test best effort * feat: better user feedback * docs: review notes * fix: build * fix: bug bot Car mode runtime state exported in backup Medium Severity The car mode preferences backup includes the carModeSilentUntil timestamp (key "B") which is runtime state, not user configuration. BTCarModeStorage uses "A" for trigger devices (user config) and "B" for silent-until timestamp (runtime state). The code passes excludeRuntimeState = false to avoid filtering out the "A" key, but this also includes "B". When restored, a stale future timestamp could cause car mode silent period to be incorrectly active on the new device, affecting notification behavior until that time passes. * fix: bug bot Missing text reset causes wrong message after permission grant Low Severity When Bluetooth permission is denied, noDevicesText.text is set to the "permission required" message. However, loadBluetoothDevices() only sets the visibility, never resetting the text. If a user denies permission, then grants it via app settings and returns, and has no paired Bluetooth devices, they'll see "Bluetooth permission is required..." instead of the correct "No known Bluetooth devices" message. * test: fix
1 parent ecf9ab2 commit 201cbe7

14 files changed

Lines changed: 2367 additions & 11 deletions

File tree

android/app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@
1313

1414
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
1515

16-
<uses-permission android:name="android.permission.BLUETOOTH" />
16+
<!-- Legacy Bluetooth permission - only needed on API 30 and below -->
17+
<uses-permission android:name="android.permission.BLUETOOTH"
18+
android:maxSdkVersion="30" />
1719
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- this is also required for BlueTooth - according to google bluetooth device discovery can be used for coarse location -->
1820

21+
<!-- Android 12+ (API 31) - Required for accessing paired Bluetooth devices -->
22+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
23+
1924
<!-- TODO: figure out how to only enable these for dev build -->
2025
<uses-permission android:name="android.permission.INTERNET" />
2126

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Calendar Notifications Plus
3+
// Copyright (C) 2025 William Harris (wharris+cnplus@upscalews.com)
4+
//
5+
// This program is free software; you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation; either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program; if not, write to the Free Software Foundation,
17+
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
//
19+
20+
package com.github.quarck.calnotify.backup
21+
22+
import kotlinx.serialization.Serializable
23+
import kotlinx.serialization.json.JsonElement
24+
25+
/**
26+
* Per-calendar enabled/disabled setting with identifying info for cross-device matching.
27+
* On import, calendars are matched by accountName+displayName.
28+
*/
29+
@Serializable
30+
data class CalendarSettingBackup(
31+
val accountName: String,
32+
val accountType: String,
33+
val displayName: String,
34+
val ownerAccount: String,
35+
val name: String,
36+
val enabled: Boolean
37+
)
38+
39+
/**
40+
* Root backup data structure.
41+
* Version field allows future migrations if the schema changes.
42+
*/
43+
@Serializable
44+
data class BackupData(
45+
val version: Int = CURRENT_VERSION,
46+
val exportedAt: Long,
47+
val appVersionCode: Long,
48+
val appVersionName: String,
49+
/** Main app settings from default SharedPreferences (excludes calendar_handled_ keys) */
50+
val settings: Map<String, JsonElement>,
51+
/** Car mode Bluetooth device settings */
52+
val carModeSettings: Map<String, JsonElement>,
53+
/** Per-calendar enabled settings with identifying info for cross-device matching */
54+
val calendarSettings: List<CalendarSettingBackup> = emptyList()
55+
) {
56+
companion object {
57+
const val CURRENT_VERSION = 2
58+
}
59+
}
60+

0 commit comments

Comments
 (0)