Screen Lock Plugin
A Flutter plugin that allows you to programmatically lock the Android device screen.
Features
- Lock the device screen programmatically
- Check device admin permission status
- Request device admin permissions
- Check current screen state (
isScreenOn) - Stream screen on/off events (
onScreenStateChanged) from a dedicated foreground service so events keep firing while the app is backgrounded - Multi-source screen-state detection (broadcast +
DisplayManager+PowerManagerpolling fallback), de-duplicated into a single stream - Adaptive foreground service type:
systemExemptedfor device-owner kiosks,specialUseotherwise - Simple and easy-to-use API
Platform Support
| Platform | Supported |
|---|---|
| Android | ✅ |
| iOS | ❌ |
| Web | ❌ |
| Windows | ❌ |
| macOS | ❌ |
| Linux | ❌ |
Android Setup
No additional manifest setup is required. The plugin automatically configures the necessary permissions, the device admin receiver, and the ScreenEventService foreground service used to deliver screen on/off events.
Notification permission (Android 13+)
While onScreenStateChanged() has an active listener, the plugin starts a foreground service that posts a persistent low-importance notification ("Screen monitor active"). The service still runs if the user denies POST_NOTIFICATIONS, but the notification will be suppressed. To make the service visible and to match user expectations, request the notification permission from your app before subscribing, for example with permission_handler:
import 'package:permission_handler/permission_handler.dart';
await Permission.notification.request();
Usage
Import the package
import 'package:screen_lock_plugin/screen_lock_plugin.dart';
Create an instance
final screenLockPlugin = ScreenLockPlugin();
Check if device admin is enabled
bool? isEnabled = await screenLockPlugin.isDeviceAdminEnabled();
if (isEnabled == true) {
print('Device admin is enabled');
} else {
print('Device admin is not enabled');
}
Request device admin permissions
await screenLockPlugin.requestDeviceAdmin();
This will show a system dialog asking the user to grant device admin permissions to your app.
Lock the screen
bool? result = await screenLockPlugin.lockScreen();
if (result == true) {
print('Screen locked successfully');
} else {
print('Failed to lock screen. Device admin may not be enabled.');
}
Check the current screen state
bool? isOn = await screenLockPlugin.isScreenOn();
print('Screen is currently ${isOn == true ? "ON" : "OFF"}');
Listen for screen on/off events
final subscription = screenLockPlugin.onScreenStateChanged().listen(
(event) {
// event is either 'SCREEN_ON' or 'SCREEN_OFF'
print('Screen event: $event');
},
onError: (Object error) {
// e.g. 'FGS_START_FAILED' if the foreground service could not start
print('Screen event error: $error');
},
);
// Later, when you no longer need events:
await subscription.cancel();
The first active listener starts ScreenEventService as a foreground service and shows an ongoing notification. When the last listener is cancelled, the service is stopped.
How It Works
This plugin uses Android's DevicePolicyManager API to lock the screen and a dedicated foreground service to monitor the display state. Here's what happens:
-
Device Admin Permissions: The app must be registered as a device administrator to lock the screen. This is a security requirement by Android.
-
User Consent: Users must explicitly grant device admin permissions through a system dialog. This cannot be done automatically.
-
Screen Lock: Once permissions are granted, the plugin can lock the screen immediately using
DevicePolicyManager.lockNow(). -
Screen Event Monitoring: While a Dart listener is attached to
onScreenStateChanged(), the plugin runsScreenEventServiceas a foreground service. It combines three independent signals and emits only real transitions:ACTION_SCREEN_ON/ACTION_SCREEN_OFFbroadcastsDisplayManager.DisplayListenerstate changes (catches AOD / doze transitions that do not broadcast on some OEMs)- A periodic
PowerManager.isInteractivepoll as a safety net against broadcast throttling
-
Adaptive Foreground Service Type (Android 14+): If the app is provisioned as the device owner, the service starts with
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTEDfor the broadest kiosk exemption. Otherwise it usesFOREGROUND_SERVICE_TYPE_SPECIAL_USE, backed by thePROPERTY_SPECIAL_USE_FGS_SUBTYPEdeclared in the manifest.
Important Notes
-
User Permission Required: Users must manually grant device admin permissions. This is a security feature and cannot be bypassed.
-
Revoking Permissions: Users can revoke device admin permissions at any time through:
- Settings → Security → Device administrators
-
Uninstalling: If users want to uninstall your app, they must first disable device admin permissions in Settings.
-
Best Practices:
- Always check if device admin is enabled before attempting to lock the screen
- Provide clear UI feedback about permission status
- Explain why your app needs this permission
Permissions
The plugin automatically adds the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
BIND_DEVICE_ADMIN— required to lock the screen viaDevicePolicyManager.FOREGROUND_SERVICE(+ theSPECIAL_USE/SYSTEM_EXEMPTEDsubtypes) — required to runScreenEventServiceso screen events are delivered reliably in the background.POST_NOTIFICATIONS— runtime permission on Android 13+ for the service's ongoing notification. The service still runs if denied, but the notification is suppressed.
Troubleshooting
Screen doesn't lock when button is pressed
- Verify device admin is enabled by checking
isDeviceAdminEnabled() - If not enabled, call
requestDeviceAdmin()to prompt the user
App won't uninstall
- Users must disable device admin permissions before uninstalling
- Go to Settings → Security → Device administrators → Disable your app
onScreenStateChanged() never emits events
- Make sure a listener is attached — the foreground service only starts while a subscription is active.
- Check the system logs for
ScreenLockPlugin/ScreenEventServicetags. Startup failures are logged there and are also surfaced on the stream as an error with codeFGS_START_FAILED. - On Android 13+, grant
POST_NOTIFICATIONSso you can see the "Screen monitor active" notification and confirm the service is running. - Some OEMs aggressively restrict background execution. Provisioning the app as a device owner (kiosk) lets the service use
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTEDand bypass most of those restrictions.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
Changelog
See CHANGELOG.md for a list of changes in each version.