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àm onChange
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_PATH
và DISPLAY_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_NAME
và RELATIVE_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 cacsh lưu trữ này, nhưng dù sao nếu bạn tìm thấy một ngoại lệ như thế, bạn vẫn có thể dễ dàng kiểm tra cho thư mục đó vì ta đã có đường dẫn của tệp khi nó được trả về từ ContentObserver
.
Source
https://proandroiddev.com/detect-screenshots-in-android-7bc4343ddce1