-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Platform
Android 13
Plugin
battery_plus
Version
6.2.2
Flutter SDK
3.29.3
Steps to reproduce
This error was reported via Sentry, but I can't reproduce it on my device. This error appears to be occurring in the combination of Android 13 and the SM-A032F device. Below is the root cause observed by Sentry Seer. Please let me know if you need any more information.
Root Cause of the Issue
Flutter app's battery_plus plugin attempts to read Android's restricted psm_switch setting, causing a SecurityException on Android 12+ devices.
The application initializes a timer to periodically check the device's low power mode status.
// lib/screen/home/data/home_data.dart
Future<void> initLowPowerModeCheckTimer() async {
try {
// ...
lowPowerModeCheckTimer = Timer.periodic(1.seconds, (_) async {
if (await battery.isInBatterySaveMode == true) showLowPowerModeDialog();
});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
This function sets up a recurring check for battery save mode, which is the entry point for the problematic operation. The try-catch
block here attempts to handle exceptions, but the SecurityException
is still propagated.
(See lib/screen/home/data/home_data.dart)
The Flutter application invokes the isInBatterySaveMode method from the battery_plus plugin.
// packages/battery_plus/battery_plus/lib/src/method_channel_battery_plus.dart (conceptual)
class MethodChannelBattery extends BatteryPlatform {
@override
Future<bool> get isInBatterySaveMode async {
final bool? isInBatterySaveMode = await _methodChannel.invokeMethod('isInBatterySaveMode');
return isInBatterySaveMode ?? false;
}
}
This is the Flutter-side abstraction that communicates with the native platform code. It uses a MethodChannel
to send a request to the Android operating system.
(See method_channel_battery_plus.dart)
The native Android code within the battery_plus plugin attempts to read the psm_switch system setting.
// Android native code within battery_plus plugin (conceptual, based on stack trace)
// at J2.a.l(SourceFile:12)
// This line conceptually represents the plugin's call:
String psmSwitch = Settings.System.getString(context.getContentResolver(), "psm_switch");
The battery_plus
plugin's Android implementation tries to determine the battery save mode status by directly querying the psm_switch
setting, which is an internal Android setting.
(See SourceFile)
The Android operating system, on versions S+ (Android 12 and above), restricts access to the @hide psm_switch setting for non-system applications.
Settings key: <psm_switch> is not readable. From S+, settings keys annotated with @hide are restricted to system_server and system apps only, unless they are annotated with @Readable.
This is the core policy enforcement by the Android OS. The psm_switch
setting is marked with @hide
, meaning it's not part of the public API and its access is now strictly controlled for security and stability reasons.
(See Settings.java)
The Android OS throws a SecurityException because the application lacks the necessary system privileges to read the restricted setting.
java.lang.SecurityException: Settings key: <psm_switch> is not readable. From S+, settings keys annotated with @hide are restricted to system_server and system apps only, unless they are annotated with @Readable.
at android.provider.Settings$NameValueCache.getStringForUser(Settings.java:3112)
at android.provider.Settings$System.getStringForUser(Settings.java:3905)
at android.provider.Settings$System.getString(Settings.java:3887)
This exception is the direct result of the Android OS denying the read operation. It clearly states the reason: the setting is restricted to system apps on Android S+ unless explicitly marked as readable.
(See Settings.java)
Code Sample
...
try {
if (await battery.isInBatterySaveMode == true) showLowPowerModeDialog();
lowPowerModeCheckTimer = Timer.periodic(1.seconds, (_) async {
if (await battery.isInBatterySaveMode == true) showLowPowerModeDialog();
});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
...
await battery.isInBatterySaveMode == true <- This is where the error occurs.
Logs
I can't reproduce the error on my device. There's no issue on my end.
Flutter Doctor
I can't reproduce the error on my device. There's no issue on my end.
Checklist before submitting a bug
- I searched issues in this repository and couldn't find such bug/problem
- I Google'd a solution and I couldn't find it
- I searched on StackOverflow for a solution and I couldn't find it
- I read the README.md file of the plugin
- I'm using the latest version of the plugin
- All dependencies are up to date with
flutter pub upgrade
- I did a
flutter clean
- I tried running the example project