I am trying to make a custom overlay for my application that will hide the screen if any screen share or unauthorized recording of my app is running.

The problem is, that I am trying to avoid using FLAG_SECURE, because it doesn't allow me to add custom elements to inform the user that the screen is being recorded and show them what is going on.

I have tried creating custom logic, and here is what I have achieved so far:

I managed to create a custom detection using DisplayManager, to catch whether recording is active. However, I found a bypass that I cannot fix. If you start recording before entering the app, your screen doesn't hide. Recording that starts outside of the app cannot be detected. The detection only works if recording starts while the app is in the background or on screen.

So far, I have tested sharing my screen and starting recording while in the app. This works great and looks really nice to inform the user what is actually happening. But this can be bypassed if recording starts outside the app before entering.

I have read forums and tried other approaches, but everyone says it is not possible without FLAG_SECURE. Using FLAG_SECURE would also block screenshots, which is not what I want. My goal is to achieve something similar to what my iOS colleague has implemented.


Here's the code I'm using:

class ScreenRecordingDetectionManager(private val context: Context) {

    private val controller: ScreenSecurityController =
        (context as SmartApplication).getScreenSecurityController()
            ?: throw IllegalStateException("ScreenSecurityController not initialized")

    private val displayListener = object : DisplayManager.DisplayListener {
        override fun onDisplayAdded(id: Int) {
            if (id != Display.DEFAULT_DISPLAY) {
                controller.showSecurityOverlay()
            }
        }

        override fun onDisplayRemoved(id: Int) {
            if (id != Display.DEFAULT_DISPLAY) {
                controller.hideSecurityOverlay()
            }
        }

        override fun onDisplayChanged(id: Int) {}
    }

    fun startMonitoring() {
        val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        dm.registerDisplayListener(displayListener, null)

        val handler = android.os.Handler(context.mainLooper)
        handler.postDelayed({
            val isExternalScreen =
                dm.displays.any { it.displayId != Display.DEFAULT_DISPLAY }
            val isMediaProjection =
                controller.isMediaProjectionRecording()

            if (isExternalScreen || isMediaProjection) {
                controller.showSecurityOverlay()
            }
        }, 200)
    }
}
class ScreenSecurityController(private val context: Context) :
    Application.ActivityLifecycleCallbacks {

    private var recordingOverlay: View? = null
    private var isRecording = false

    fun showSecurityOverlay() {
        isRecording = true
        applyOverlayToCurrentActivity()
    }

    fun hideSecurityOverlay() {
        isRecording = false
        recordingOverlay?.visibility = View.GONE
    }

    private fun applyOverlayToCurrentActivity() {

        if (recordingOverlay != null && recordingOverlay?.parent != null)
            return

        val activity = (context as? SmartApplication)?.currentActivity ?: return

        val root = activity.findViewById<ViewGroup>(android.R.id.content)


        recordingOverlay = LayoutInflater.from(activity)
            .inflate(R.layout.screen_recording_overlay, root, false)
        root.addView(recordingOverlay)

        recordingOverlay?.bringToFront()
        recordingOverlay?.visibility = if (isRecording) View.VISIBLE else View.GONE
    }

    override fun onActivityResumed(activity: Activity) {
        if (isRecording) applyOverlayToCurrentActivity()
    }

    override fun onActivityCreated(a: Activity, b: Bundle?) {}
    override fun onActivityStarted(a: Activity) {}
    override fun onActivityPaused(a: Activity) {}
    override fun onActivityStopped(a: Activity) {}
    override fun onActivitySaveInstanceState(a: Activity, outState: Bundle) {}
    override fun onActivityDestroyed(a: Activity) {}

    @SuppressLint("PrivateApi")
    fun isMediaProjectionRecording(): Boolean {
        return try {
            val service = Class.forName("android.os.SystemProperties")
            val getter = service.getMethod("get", String::class.java)
            val value = getter.invoke(null, "sys.service.media.projection") as String
            value.contains("running", ignoreCase = true)
        } catch (e: Exception) {
            false
        }
    }
}

1 Reply 1

Use FLAG_SECURE. That is the official way of hiding data from those types of apps. Anything else is a hack- you will miss corner cases. You already found one, I can think of a dozen others. They added the official method for a reason. There is no way to assure that you get all the corner cases other than using the official method.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.