Implementing screen recording protection in mobile applications
Screen recording — more serious threat than screenshot. It captures PIN entry, authorization process, document viewing. On iOS built-in Screen Recorder and AirPlay-mirroring work systemically. On Android MediaProjection API is available to third-party apps with user permission (but malicious software gets it via deception).
Android: FLAG_SECURE against MediaProjection
FLAG_SECURE — main protection:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
setContentView(R.layout.activity_main)
}
Flag blocks capture via MediaProjection: screen recording gets black rectangle instead of Activity contents. Works for system recorder and third-party apps using MediaProjection API.
For Jetpack Compose — same approach at Activity level, Compose content inside same Window.
Limitation: hardware-based capture (HDMI capture card, physical camera) not blocked. This is limitation of whole platform, not specific implementation.
iOS: detection and blocking
iOS doesn't provide API for blocking recording. But UIScreen.isCaptured and UIScreen.capturedDidChangeNotification — powerful instrument.
isCaptured returns true in following situations:
- Built-in screen recording active (Control Center Screen Recording)
- AirPlay mirroring active
- QuickTime Player captures screen via Lightning/USB
Doesn't trigger for single-frame screenshot — important distinction from Android FLAG_SECURE.
Proper reaction — not crash, not logout, but hiding sensitive content:
private var cancellables = Set<AnyCancellable>()
func setupScreenCaptureProtection() {
NotificationCenter.default.publisher(
for: UIScreen.capturedDidChangeNotification
)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.updateContentVisibility()
}
.store(in: &cancellables)
// check current state on launch
updateContentVisibility()
}
private func updateContentVisibility() {
let isBeingRecorded = UIScreen.main.isCaptured
sensitiveContainerView.isHidden = isBeingRecorded
if isBeingRecorded {
recordingWarningView.isHidden = false
}
}
recordingWarningView — placeholder replacing data, shown to user during recording instead of data. Both protection and UX explanation why content hidden.
SwiftUI variant
struct SensitiveContentView: View {
@State private var isScreenBeingRecorded = UIScreen.main.isCaptured
var body: some View {
Group {
if isScreenBeingRecorded {
RecordingBlockerView()
} else {
ActualSensitiveContent()
}
}
.onReceive(
NotificationCenter.default.publisher(
for: UIScreen.capturedDidChangeNotification
)
) { _ in
isScreenBeingRecorded = UIScreen.main.isCaptured
}
}
}
Flutter and React Native
Both cross-platform frameworks require native plugins for this functionality.
Flutter: flutter_windowmanager on Android sets FLAG_SECURE. For iOS need platform channel with native Swift implementation — no reliable ready packages, write ourselves. Platform channels in this case — standard.
React Native: react-native-flag-secure-android for Android. For iOS — native module via NativeModules.
Task switcher audit
Separate vulnerability — screenshot iOS and Android make automatically when app goes to background (for App Switcher preview). This snapshot saved in system cache.
iOS: cover with overlay View in sceneWillResignActive:
func sceneWillResignActive(_ scene: UIScene) {
overlayView.isHidden = false
}
func sceneDidBecomeActive(_ scene: UIScene) {
overlayView.isHidden = true
}
Android: FLAG_SECURE covers this case — App Switcher preview also black.
Full implementation of screen recording protection on Android + iOS, including App Switcher preview protection and isCaptured state handling — 1–3 days depending on framework and number of screens with sensitive content.







