I am currently developing an application for Android and wanted to know how to detect a screenshot. I tried with FileObserver but the problem is that all events are detected ( when device goes into sleep, message, etc. ) . How to detect only screenshot ?
Thank you in advance !
Screenshots will only be detected on Mobile devices but screenshot notifications can display on both Mobile and Desktop. Users may not Recall a screenshot notification once action taken. How will this affect Burn-on-read (BOR) settings?
Use the Android Screenshot ShortcutPress and hold the Power + Volume Down buttons at the same time, and you'll see a brief onscreen animation followed by a confirmation in the notification bar that the action was successful. There's a knack to getting the timing right.
How did you use FileObserver
to detect screen shot creation? When using FileObserver
, only monitor the file creation event in screen shot directory.
String path = Environment.getExternalStorageDirectory()
+ File.separator + Environment.DIRECTORY_PICTURES
+ File.separator + "Screenshots" + File.separator;
Log.d(TAG, path);
FileObserver fileObserver = new FileObserver(path, FileObserver.CREATE) {
@Override
public void onEvent(int event, String path) {
Log.d(TAG, event + " " + path);
}
};
fileObserver.startWatching();
Don't forget to declare corresponding permissions to access content in SD card.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Another solution to detect the screen shot is using ContentObserver
, because there will be a record inserted to the system media database after screen shot. Following is the code snippet using ContentObserver
to monitor the event. By using ContentObserver
, it's not necessary to declare write/read external storage
permissions, but you have to do some filters on the file name to make sure it's a screen shot event.
HandlerThread handlerThread = new HandlerThread("content_observer");
handlerThread.start();
final Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
new ContentObserver(handler) {
@Override
public boolean deliverSelfNotifications() {
Log.d(TAG, "deliverSelfNotifications");
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
Log.d(TAG, "onChange " + uri.toString());
if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/[0-9]+")) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri, new String[] {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA
}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
final String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
// TODO: apply filter on the file name to ensure it's screen shot event
Log.d(TAG, "screen shot added " + fileName + " " + path);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
super.onChange(selfChange, uri);
}
}
);
Updated
If you use second method, you have to request READ_EXTERNAL_STORAGE
after version Android M, otherwise it will throw SecurityException
. For more information how to request runtime permission, refer here.
I have improve the code from alijandro's comment to make it easy-to-use class and fix the problem when content observer has detect the image from camera (should be screenshot image only). Then wrap it to delegate class for convenient to use.
• ScreenshotDetectionDelegate.java
public class ScreenshotDetectionDelegate {
private WeakReference<Activity> activityWeakReference;
private ScreenshotDetectionListener listener;
public ScreenshotDetectionDelegate(Activity activityWeakReference, ScreenshotDetectionListener listener) {
this.activityWeakReference = new WeakReference<>(activityWeakReference);
this.listener = listener;
}
public void startScreenshotDetection() {
activityWeakReference.get()
.getContentResolver()
.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver);
}
public void stopScreenshotDetection() {
activityWeakReference.get().getContentResolver().unregisterContentObserver(contentObserver);
}
private ContentObserver contentObserver = new ContentObserver(new Handler()) {
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (isReadExternalStoragePermissionGranted()) {
String path = getFilePathFromContentResolver(activityWeakReference.get(), uri);
if (isScreenshotPath(path)) {
onScreenCaptured(path);
}
} else {
onScreenCapturedWithDeniedPermission();
}
}
};
private void onScreenCaptured(String path) {
if (listener != null) {
listener.onScreenCaptured(path);
}
}
private void onScreenCapturedWithDeniedPermission() {
if (listener != null) {
listener.onScreenCapturedWithDeniedPermission();
}
}
private boolean isScreenshotPath(String path) {
return path != null && path.toLowerCase().contains("screenshots");
}
private String getFilePathFromContentResolver(Context context, Uri uri) {
try {
Cursor cursor = context.getContentResolver().query(uri, new String[]{
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA
}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
} catch (IllegalStateException ignored) {
}
return null;
}
private boolean isReadExternalStoragePermissionGranted() {
return ContextCompat.checkSelfPermission(activityWeakReference.get(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
public interface ScreenshotDetectionListener {
void onScreenCaptured(String path);
void onScreenCapturedWithDeniedPermission();
}
}
• ScreenshotDetectionActivity.java
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public abstract class ScreenshotDetectionActivity extends AppCompatActivity implements ScreenshotDetectionDelegate.ScreenshotDetectionListener {
private static final int REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSION = 3009;
private ScreenshotDetectionDelegate screenshotDetectionDelegate = new ScreenshotDetectionDelegate(this, this);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkReadExternalStoragePermission();
}
@Override
protected void onStart() {
super.onStart();
screenshotDetectionDelegate.startScreenshotDetection();
}
@Override
protected void onStop() {
super.onStop();
screenshotDetectionDelegate.stopScreenshotDetection();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
showReadExternalStoragePermissionDeniedMessage();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
public void onScreenCaptured(String path) {
// Do something when screen was captured
}
@Override
public void onScreenCapturedWithDeniedPermission() {
// Do something when screen was captured but read external storage permission has denied
}
private void checkReadExternalStoragePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestReadExternalStoragePermission();
}
}
private void requestReadExternalStoragePermission() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_EXTERNAL_STORAGE_PERMISSION);
}
private void showReadExternalStoragePermissionDeniedMessage() {
Toast.makeText(this, "Read external storage permission has denied", Toast.LENGTH_SHORT).show();
}
}
• MainActivity.java
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends ScreenshotDetectionActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onScreenCaptured(String path) {
Toast.make(this, path, Toast.LENGTH_SHORT).show();
}
@Override
public void onScreenCapturedWithDeniedPermission() {
Toast.make(this, "Please grant read external storage permission for screenshot detection", Toast.LENGTH_SHORT).show();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With