device_safety_info 1.5.3
device_safety_info: ^1.5.3 copied to clipboard
Device security toolkit for Flutter - root/jailbreak, hook, and debugger detection, screenshot and clipboard protection, overlay-attack defenses, VPN and update checks.
1.5.3 #
- Breaking:
REQUEST_INSTALL_PACKAGESis no longer declared by the plugin's ownAndroidManifest.xml, so it no longer merges into every consuming app by default — previously every integrator paid the Play Console sensitive-permission review cost for this permission whether or not they usedisUnknownSourcesEnabled, and had to know to opt out viatools:node="remove"if they didn't want it. Apps that callisUnknownSourcesEnabledmust now add<uses-permission android:name= "android.permission.REQUEST_INSTALL_PACKAGES" />to their ownAndroidManifest.xml(see README's Permissions (Android) section). Apps that don't use the feature need no manifest change. Without the permission,isUnknownSourcesEnabledalready degraded tofalsevia the existingSecurityExceptioncatch inUnknownSourcesCheck.kt, so no native code changed.
1.5.2 #
- Fix:
blockScreenshots(true)crashed on iOS withCALayerInvalid("layer ... is a part of cycle in its layer tree") on the very first call, a guaranteed, 100%-reproducible crash on real devices and simulators alike.ScreenshotProtectionHandler.enableScreenshotBlocking()added the secureUITextFieldas a subview of the key window, then re-parented the window's ownCALayerinto that field's secure sublayer — but since the field already lived inside the window's view hierarchy, its secure sublayer was already a descendant ofwindow.layer, so the re-parent made the window layer its own ancestor, an illegal CoreAnimation cycle. The secure field is now hosted in its own separateUIWindowso its layer tree never overlaps the target window's before the re-parent happens.
1.5.1 #
- Fix:
isRootedDevice()crashed with a fatalNoSuchMethodErroron real Android 7.0/7.1 (API 24/25) devices — the exact floor this package's README lists as the minimum supported version (#19).ShellExecutor.ktcalledProcess.destroyForcibly()andProcess.waitFor(long, TimeUnit)unconditionally; both were added in API 26. SinceisRootedDevice()typically runs unconditionally at app startup, this was a guaranteed, 100%-reproducible crash on API 24/25 hardware, not an edge case. Both calls are now guarded byBuild.VERSION.SDK_INT, with a polling-based bounded wait on API 24/25 so the 200ms timeout behavior is preserved instead of falling back to an unboundedwaitFor(). Thanks to @Enrrique-Rojas for the detailed report and diagnosis!
1.5.0 #
Internal restructuring — feature modules, not a rewrite. lib/, the Android Kotlin plugin, and
the iOS Swift plugin are now organized as one vertical slice per feature (root detection, screen
capture, screenshot, clipboard, overlay-attack, call activity, VPN, etc.) instead of a handful of
large files implementing everything inline. This is purely structural — every existing top-level
import (e.g. package:device_safety_info/vpn_check.dart) and every existing DeviceSafetyInfo
member keeps its exact signature and behavior; nothing here is a breaking change. compileSdk
(and the example app's targetSdk) are bumped to 37.
New — screenshot overlay modes:
DeviceSafetyInfo.setScreenshotOverlayMode(mode: ScreenshotOverlayMode, ...)/clearScreenshotOverlayMode()show a real, visible blur/color/image overlay over the active screen whenever a capture or recording is detected — a branded "content hidden" placeholder instead of the plain black rectangleblockScreenshotsalone produces. FLAG_SECURE (Android) and the iOS secure-layer trick prevent the OS from rendering anything at all into a capture, so this overlay is a visible, on-screen-only effect, shown reactively while a capture/recording is active. Android blur requires API 31+ (degrades to a translucent scrim below that); iOS blur uses a system material blur style. Android + iOS.DeviceSafetyInfo.isScreenshotBlocked/toggleScreenshotBlocking()— convenience query/toggle alongside the existingblockScreenshots.
New — screen-recording detection:
ScreenRecordingDetector.isSupported/onScreenRecordingChanged(plusonScreenRecordingStarted/onScreenRecordingStoppedconvenience filters) detect an active screen-recording session, distinct fromisScreenCaptured/onScreenCapturedChanged(which covers screen mirroring/external-display capture). Android: backed by the realWindowManager.addScreenRecordingCallbackAPI, API 35+ only —isSupportedreportsfalsebelow that rather than guessing. iOS has no API distinguishing "recording" from "mirroring/AirPlay" — both surface through the sameUIScreen.isCapturedsignalisScreenCapturedalready uses, soisSupportedis alwaystruethere and the two streams report identically; this is documented onScreenRecordingDetectoritself.- Fix: declared the
android.permission.DETECT_SCREEN_RECORDINGmanifest permission this feature requires (missing in the initial implementation), and made registration fail soft — some OEM builds deny it at runtime even when declared (observed on a Samsung device), which previously crashed with an uncaughtSecurityExceptioninstead of delivering apermission_deniedstream error like the plugin's other permission-gated streams do.
New — SecureScreen widget: a declarative, ref-counted wrapper (SecureScreen(child: ...))
that engages blockScreenshots while mounted and releases it once no SecureScreen remains in the
tree — pure Dart, no native code of its own. Nested/sibling SecureScreens compose correctly.
NewVersionChecker/VersionStatus hardening:
- Fix:
VersionStatus.canUpdateno longer throws on a non-purely-numeric version segment (e.g."1.2.3-beta") — version comparison now degrades unparseable segments to0instead of crashing. - New:
NewVersionChecker(minAppVersion: ...)+VersionStatus.urgency(UpdateUrgency.none/optional/required) — force/required-update support. The threshold is always developer-supplied (your own remote config, or hardcoded), never scraped from the store, since anything parsed out of store HTML/metadata is one layout change away from breaking. - New:
NewVersionChecker(iOSAppStoreId: ...)— an optional numeric App Store ID fallback, retried when the bundle-ID-keyed lookup returns no results (a reported failure mode even for live, published apps). - Fix: both store lookups now fail soft (return
null) on any unexpected response shape, instead of letting aTypeError/FormatExceptionescapegetVersionStatus(). - Fix:
simpleHttpGetnow has a 10s timeout; a hung store endpoint could previously hanggetVersionStatus()indefinitely. - Deliberately not implemented: release-notes/"what's new" extraction — this field is unreliable and inconsistently formatted across both stores; documented as a known limitation in the README instead of shipping a frequently-broken scraper for it.
1.4.1 #
- Fix: iOS builds failing under Flutter's Swift Package Manager integration with
product 'device-safety-info' ... not found in package 'device_safety_info'(#17). Flutter's generatedFlutterGeneratedPluginSwiftPackagerequests each plugin's SPM library product by its pubspec name with underscores replaced by hyphens (device-safety-info), butPackage.swiftdeclared the product with an underscore. The product name now matches the convention used by every other Flutter plugin. Thanks to @jey-avono for reporting and diagnosing this!
1.4.0 #
New checks — Android banking-malware defenses (added in response to advisory coverage of Android banking trojans like TrickMo/PhantomCall):
- New: Notification Listener enumeration —
enabledNotificationListeners/isAnyNotificationListenerEnabledsurface which apps currently hold notification-listener access (the mechanism banking trojans commonly abuse to intercept OTP/SMS notifications). Android only. - New: Unknown-sources / sideloading check —
isUnknownSourcesEnabled. On Android 8+ this can only answer "has this app been granted install rights" (not "has some other app"), a real API limitation documented in the getter's own doc comment. RequiresREQUEST_INSTALL_PACKAGES(query-only, never installs anything) — strip it viatools:node="remove"in your manifest if you don't use this check. - New: Call-screening role —
isCallScreeningRoleAvailable,isCallScreeningRoleHeldByThisApp,openCallScreeningRoleSettings(). Android'sRoleManager.getRoleHolders()(which would reveal which app holds the role) is a privileged system API unavailable to third-party apps — these three cover what's actually achievable: capability check, self-check, and a settings deep-link so the user can review the current holder themselves. Android only, API 29+. - New: Call activity detection —
onCallActivityChangedstream +isCallActivegetter detect when any call (native SIM or a VoIP call from WhatsApp/Teams/Skype/Meet/imo/etc.) starts or ends, without identifying which app is calling (not achievable on either platform). Android:TelephonyManager(SIM, needsREAD_PHONE_STATE) + system-wideAudioManagerrouting state (any VoIP app, generically). iOS:CXCallObserver(CallKit) +AVAudioSessioninterruption notifications. Detect-only, like every other stream in this plugin — no lockdown/navigation policy is embedded. Native listeners only run while the stream has an active subscriber.
Dependency removal (eliminates consumer version-conflict risk from this plugin's own
dependencies:):
- Removed
connectivity_plus,package_info_plus, andhttp— replaced with a nativedevice_safety_info/connectivity_eventsEventChannel, a nativegetPackageInfoMethodChannel call, and a minimaldart:io HttpClient-based helper (lib/src/http/simple_http_get.dart) respectively.VPNCheck,NewVersionChecker, andIOCDomainBlockerare unaffected from the outside.
Toolchain modernization (Flutter 3.47 plugin-template baseline):
- Breaking (iOS): minimum iOS version raised
13.0→16.0. - Fix (iOS — Swift Package Manager):
Package.swiftmoved from the flatios/Package.swifttoios/device_safety_info/Package.swift— the path Flutter's tooling actually scans for plugin SPM support (Plugin.pluginSwiftPackageManifestPathinflutter_tools). The previous flat location was never discovered by Flutter's build system, so SPM support was silently non-functional despite being present; only the CocoaPods path was ever exercised. The native C FFI source now lives in its own SPM target (device_safety_ffi) since SwiftPM doesn't support mixed Swift+C sources in one target. - Dependency: Android toolchain baseline bumped to match the Flutter 3.47 plugin template — Gradle
8.14→9.3.1, Android Gradle Plugin8.12.1→9.1.0, Kotlin2.2.20→2.4.0. - Dependency:
flutter_lintsany→^6.0.0. - Removed the plugin's Kotlin-level (
android/src/test/kotlin) unit test in favor of relying solely on the Dart-level test suite (test/device_safety_info_test.dart) — one less native test dependency (kotlin-test,mockito-core) to keep in sync, and this plugin's actual public surface is the Dart API.
1.3.0 #
- Fix (Android — 16 KB page size):
libdevice_safety_ffi.sois now linked with-Wl,-z,max-page-size=16384/common-page-size=16384, fixing Google Play Console's "native library not 16 KB compatible" warning forarm64-v8aandx86_64. - Fix (Android — build): the plugin module now declares its own self-contained
buildscriptclasspath and explicitly applies the Kotlin Android Gradle plugin, instead of relying on transitive application via Flutter's Gradle plugin — that assumption didn't hold under all AGP/Gradle declarative-plugins{}configurations, causing Kotlin sources to silently not compile andcannot find symbol DeviceSafetyInfoPluginbuild failures (#14). - New: Overlay Attack Detection —
onOverlayAttackDetectedstream andblockTouchesWhenObscured()detect/block touches delivered while another app is drawing an overlay on top of yours (tapjacking). Android only; throwsPlatformException('UNSUPPORTED_PLATFORM', ...)on iOS, where app sandboxing makes cross-app overlays structurally impossible. - New: Clipboard Protection —
copyToClipboard()(withsensitive+autoClearoptions),clearClipboard(), andonClipboardChangedstream. Android:ClipDescription.EXTRA_IS_SENSITIVE(API 33+). iOS:UIPasteboard.expirationDate/.localOnly. Android + iOS. - New:
IOCDomainBlocker— lightweight IOC/C2 domain-reputation lookup (isBlocked,updateBlocklist,loadRemoteBlocklist) to wire into your own HTTP client or WebView guard. Pure Dart, no native dependency. Android + iOS. - New: Malware Package Detection —
MalwarePackageDetector.isPackageInstalled()/scanKnownMalware()check specific package names against a list you supply. Android only. Requires declaring each package name in your app's own<queries>manifest block (Android 11+ package visibility filtering) — this plugin deliberately doesn't request the broaderQUERY_ALL_PACKAGESpermission, which Google Play gates behind manual approval and would be merged into every app depending on this plugin. - New: Accessibility Abuse Detection —
DeviceSafetyInfo.enabledAccessibilityServices/isAnyAccessibilityServiceEnabledreadSettings.Secure.ENABLED_ACCESSIBILITY_SERVICES. Android only, no new permission. - New: Play Protect Status —
DeviceSafetyInfo.playProtectStatusreads thepackage_verifier_user_consentOS setting Play Protect's toggle controls. Android only, no new permission or dependency. (SafetyNet's Verify Apps API, the old documented way to read this, was fully retired in January 2025.) - New: Idle Session Timeout —
IdleTimeoutGuardwidget fires a callback after a period of no touch activity anywhere in the wrapped subtree. Pure Dart, no native code, Android + iOS. - New: Risk Summary —
RiskSummary.evaluate()aggregates the rooted/hooked/debugger/screen-capture/VPN/ screen-lock checks into a list of plain-languageRiskFlags. Pure Dart, no new platform channel calls.
1.2.0 #
- Fix (Android — ANR):
isRootedDeviceandisHookednow run on a background thread pool — eliminates main-thread shell spawning and ANR risk. - Fix (Android — Performance):
SystemPropsCheckernow reads system properties viaandroid.os.SystemPropertiesreflection (zero-cost cache read) before falling back togetpropshell spawn — worst-case latency for 4 property checks drops from ~800 ms to near-zero. - Fix (Android):
ShellExecutormigrated fromRuntime.exec()toProcessBuilder— stdout is now drained concurrently withwaitFor, eliminating a race condition wherereadLine()blocked after the timeout expired. - Fix (Android): API-34
ScreenCaptureCallbackexecutor was never shut down onstopScreenshotDetection()— fixed resource leak. - New (Android): 30-second TTL result cache for
isRootedDeviceandisHooked— repeated polls within the window return immediately without spawning any processes. - Dependency (Android): Kotlin updated
1.9.22→2.2.0; Android Gradle Plugin8.2.2→8.12.1. - Dependency (iOS SPM):
swift-tools-versionbumped5.9→6.0(compiles in Swift 5 language mode — no source changes needed). - Dependency (iOS SPM): IOSSecuritySuite minimum version raised from
1.9.0to1.9.11. - Dependency (Dart): Dart SDK floor raised to
>=3.5.0; Flutter floor raised to>=3.24.0— this also fixes the "Swift Package Manager not supported" flag on pub.dev (pub.dev requires Flutter ≥ 3.19.0 to recognise SPM support).flutter_lintspinned to^6.0.0.
1.1.0 #
- New:
onScreenshotTakenstream — fires when the user takes a screenshot. Android API 34+: usesActivity.ScreenshotCallback(no permission needed). Android API 24–33: usesMediaStoreContentObserver(host app must holdREAD_MEDIA_IMAGESat runtime). iOS:UIApplication.userDidTakeScreenshotNotification(no permission needed). - New:
setRecentsOverlay({int argbColor})— shows a solid-color overlay over the app thumbnail in the recent-apps switcher. Automatically shown on background, hidden on foreground. Android + iOS. - New:
clearRecentsOverlay()— removes the recents overlay. - Fix:
blockScreenshots()now works on iOS via theUITextField.isSecureTextEntrylayer trick — the key window'sCALayeris re-parented into the text field's secure sublayer, which the system protects from screenshots and recordings. - New:
dart:ffinative C/C++ layer — Frida/proc/self/mapsscan + port scan (27042/27043), rootstat()check, and debuggerTracerPidcheck all run below the JVM/Swift runtime, making them significantly harder to hook - New: Swift Package Manager (SPM) support via
Package.swift— Flutter 3.19+ projects can now resolve the plugin without CocoaPods - New:
isDebuggerAttachedAPI — detects attached debuggers via native sysctl (iOS) and TracerPid (Android) - New:
isHookednow implemented on iOS viaIOSSecuritySuite.amIReverseEngineered() - New:
checkFridaByMaps()andcheckRootFilesNative()exposed as standalone public APIs - Fix:
isRootedDevicenow combines native Cstat()check with JVM-level check — false negatives from hookedFile.exists()no longer silently bypass detection - Fix:
isVPNCheckandisRootedDevicewere returningtrueas default on null/error — corrected tofalse - Fix:
ScreenCaptureDetectorwas flagging HDMI monitors and Chromecast as screen captures — fixed withFLAG_PRESENTATIONcheck - Fix:
ro.debuggable=1was incorrectly flagging all developer/debug builds as rooted — removed from root detection - Fix:
com.google.android.packageinstallerwas listed as a trusted store — it is the APK sideload installer; removed - Fix: iOS
#if TARGET_OS_SIMULATORC macro silently had no effect in Swift — corrected to#if targetEnvironment(simulator) - Fix: iOS
UIScreen.main.isCaptureddeprecated in iOS 16 — replaced with scene-based API with fallback - Fix:
blockScreenshots/hideMenureturned success silently whenActivitywas null — now returnserror("NO_ACTIVITY") - Fix:
exitProcess(0)was called beforeresult.success()— Dart Future now resolves before process exits - Fix:
DisplayListenerwas not unregistered on engine detach — memory leak fixed - Fix:
VPNCheckstream now emits initial VPN state immediately on creation - Fix:
ShellExecutortimeout increased from 50 ms to 200 ms; stderr now drained to prevent process hangs - Fix: Production
print()calls replaced withdebugPrint()throughout - Fix: Podspec metadata (version, description, homepage, author) updated from placeholder values
- Removed:
ro.debuggablefalse-positive root indicator - Removed:
com.google.android.packageinstallerfrom trusted stores - Removed: Unused
LaunchModeVersionenum - Removed: Dead pre-API-17 code path in
DevelopmentModeCheck(minSdk is 24)
1.0.3 #
- @magnus-lpa thank you for contributing screen lock issue in iOS
- @UADACID thank you for pointing out 16KB issue in Android fixed
1.0.2 #
Note: This release has breaking changes. On Android plugin now requires the following:
- Android Gradle Plugin >=8.12.1
- Gradle wrapper >=8.13
- Kotlin 2.2.0
1.0.1 #
- Android isRealDevice check issue fixed
1.0.0 #
- Dependency updated
- iOS issue fixed
- Application is installed from store check feature added
- Local and store version check feature added
0.0.9 #
- Dependency updated
- iOS issue fixed
- @jiazeh thank you for contributing
- AndroidManifest.xml issue fixed
0.0.8 #
- iOS issue fixed
0.0.7 #
- Dependency updated
- iOS issue fixed
0.0.6 #
- Dependency updated
- VPN module modification
0.0.5 #
- Dependency updated
- AGP version updated
- Kotlin version updated
- Code refactoring
0.0.4 #
- Dependency updated
- AGP version updated
- Kotlin version updated
- Code refactoring
0.0.3 #
- VPN detection issue fixed in iOS
0.0.2 #
- Example project and documentation updated
0.0.1 #
- Flutter JailBreak, Rooted, Emulator/Simulator, External storage, VPN Detector, Application Update Checker and Screen Lock detection.