这是indexloc提供的服务,不要输入任何密码
Skip to content

[Bug]: <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. #3626

@RUOK90

Description

@RUOK90

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions