Skip to content

Dashboard

Detect Screenshots trong Android

Created by Admin

Sau một thời gian tìm hiểu thì mình biết được rằng Android không cung cấp một API chính thức nào dành cho việc phát hiện người dùng có chụp ảnh màn hình hay không. Tuy nhiên vẫn có cách để giải quyết điều đó.

Bạn đã thắc mắc tại sao các ứng dụng như Snapchat và Instagram có thể phát hiện chụp ảnh màn hình ngay khi bạn chụp chưa? Trong bài viết này, chúng ta sẽ khám phá cách thực hiện điều đó.

Cách tiếp cận đơn giản là kiểm tra các ảnh trong thiết bị của người dùng xem có ảnh chụp màn hình mới được lưu vào thư mục "Screenshots" hay chưa ?.

Code thôi

Ta sẽ sử dụng một ContentObserver để quan sát bất kỳ sự thay đổi nào về hình ảnh trên máy của người dùng. Nó cần một tham số là Handler và hàmonChange sẽ được gọi khi có sự thay đổi của URI mà ta xác định.

contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
                override fun onChange(selfChange: Boolean, uri: Uri?) {
                    super.onChange(selfChange, uri)               
                }
            }

Tiếp theo, ta cần register thằng ContentObserver, hàm registerContentObserver cần 3 tham số. Đầu tiên là URI mà ContentObserver theo dõi. Tham số thứ hai notifyForDescendants ta để là true để quan sát sự thay đổi của các URI bắt đầu bằng URI mà ta đã chỉ định. Cuối cùng là thằng ContentObserver mà ta muốn register.

 context.contentResolver.registerContentObserver(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                true,
                contentObserver 
            )

Khi ta đã register ContentObserver rồi thì để xem ta sẽ nhận biết việc chụp ảnh màn hình như nào nhé. Ý tưởng đơn giản là bất cứ khi nào có sự thay đổi trong các tệp media trong khi người dùng đang sử dụng app, ta sẽ nhận được URI và sau đó ta sẽ trích xuất đường dẫn của tệp từ URI đó. Ta có thể sử dụng thuộc tính DATA của API MediaStore.Images để lấy đường dẫn tệp nhưng thuộc tính đó đã deprecated trên API 29 rồi. Trong API 29, một thuộc tính mới có tên là RELATIVE_PATH đã được thay thế. Vì vậy, đối với các thiết bị có API 29 trở lên, ta có thể sử dụng RELATIVE_PATHDISPLAY_NAME để lấy đường dẫn tệp của tệp ảnh chụp màn hình và đối với các thiết bị khác, ta vẫn có thể tiếp tục sử dụng thuộc tính DATA.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            queryRelativeDataColumn(uri)
        } else {
            queryDataColumn(uri)
        }

Quan sát đoạn code dưới đây, ta đang truy vấn thuộc tính DATA bằng cách sử dụng URI đã được trả về từ ContentObserver. Nếu đường dẫn đó chứa chuỗi “screenshot” ở bất kỳ đâu, ta có thể kết luận rằng người dùng đã chụp ảnh màn hình.

 private fun queryDataColumn(uri: Uri) {
        val projection = arrayOf(
            MediaStore.Images.Media.DATA
        )
        context.contentResolver.query(
            uri,
            projection,
            null,
            null,
            null
        )?.use { cursor ->
            val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
            while (cursor.moveToNext()) {
                val path = cursor.getString(dataColumn)
                if (path.contains("screenshot", true)) {
                    // do something
                }
            }
        }
    }

Tương tự, ta có thể được thực hiện cho API 29 với việc sử dụng DISPLAY_NAMERELATIVE_PATH như sau:

private fun queryRelativeDataColumn(uri: Uri) {
        val projection = arrayOf(
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.RELATIVE_PATH
        )
        context.contentResolver.query(
            uri,
            projection,
            null,
            null,
            null
        )?.use { cursor ->
            val relativePathColumn =
                cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
            val displayNameColumn =
                cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
            while (cursor.moveToNext()) {
                val name = cursor.getString(displayNameColumn)
                val relativePath = cursor.getString(relativePathColumn)
                if (name.contains("screenshot", true) or
                    relativePath.contains("screenshot", true)
                ) {
                    // do something
                }
            }
        }
    }

Đừng quên unregister ContentObserver khi người dùng không còn sử dụng app nữa. Ta đưa nó vào onStop.

context.contentResolver.unregisterContentObserver(contentObserver)

Vì ta đang truy cập vào các tệp media nên sẽ cần thêm permission này trong file AndroidManifest.xml

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Từ đây, nếu bạn biết bất kỳ ứng dụng nào theo dõi ảnh chụp màn hình trong khi đang sử dụng ứng dụng đó, bạn chỉ cần thu hồi quyền này và ứng dụng sẽ không thể theo dõi khi bạn chụp ảnh màn hình nữa, mình đã thử làm điều này trong ứng dụng Instagram và họ không thể phát hiện được khi mình chụp ảnh màn hình. Nếu thử bên Snapchat, họ sẽ không cho phép bạn sử dụng ứng dụng.

Tuy nhiên, giải pháp này sẽ chỉ hoạt động đối với các thiết bị lưu trữ ảnh chụp màn hình trong thư mục "Screenshots" hoặc tên tệp có chứa chuỗi "screenshot". Một số device sẽ thay đổi cash lưu trữ này, nhưng dù sao nếu bạn tìm thấy một ngoại lệ như vậy, bạn có thể dễ dàng kiểm tra cho thư mục đó vì bạn đã có đường dẫn tệp khi nó được trả về từ ContentObserver.

Source:

https://proandroiddev.com/detect-screenshots-in-android-7bc4343ddce1 https://github.com/nikit19/ScreenshotDetector

Source: https://viblo.asia/p/detect-screenshots-trong-android-yMnKM0RN57P