Detect only screenshot with FileObserver Android

前端 未结 4 1452
失恋的感觉
失恋的感觉 2020-12-13 07:25

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

相关标签:
4条回答
  • 2020-12-13 07:54

    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.

    0 讨论(0)
  • 2020-12-13 08:01

    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();
        }
    }
    
    0 讨论(0)
  • 2020-12-13 08:12

    You can create FileObserver that only monitors screenshot directory plus only trigger events for file or directory creation. For more information click here.

    0 讨论(0)
  • 2020-12-13 08:13

    I made a git project for Android screenshot detection using Content Observer.

    It is working fine from API 14 to the most recent version (at the time of posting).


    1.ScreenShotContentObserver .class
    (original screenshot delete -> inform screenshot taken and give screenshot bitmap )

    public class ScreenShotContentObserver extends ContentObserver {
    
        private final String TAG = this.getClass().getSimpleName();
        private static final String[] PROJECTION = new String[]{
                MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.DATE_ADDED, MediaStore.Images.ImageColumns._ID
        };
        private static final long DEFAULT_DETECT_WINDOW_SECONDS = 10;
        private static final String SORT_ORDER = MediaStore.Images.Media.DATE_ADDED + " DESC";
    
        public static final String FILE_POSTFIX = "FROM_ASS";
        private static final String WATERMARK = "Scott";
        private ScreenShotListener mListener;
        private ContentResolver mContentResolver;
        private String lastPath;
    
        public ScreenShotContentObserver(Handler handler, ContentResolver contentResolver, ScreenShotListener listener) {
            super(handler);
            mContentResolver = contentResolver;
            mListener = listener;
        }
    
        @Override
        public boolean deliverSelfNotifications() {
            Log.e(TAG, "deliverSelfNotifications");
            return super.deliverSelfNotifications();
        }
    
        @Override
        synchronized public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                //above API 16 Pass~!(duplicated call...)
                return;
            }
            Log.e(TAG, "[Start] onChange : " + selfChange);
            try {
                process(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                Log.e(TAG, "[Finish] general");
            } catch (Exception e) {
                Log.e(TAG, "[Finish] error : " + e.toString(), e);
            }
        }
    
        @Override
        synchronized public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange, uri);
            Log.e(TAG, "[Start] onChange : " + selfChange + " / uri : " + uri.toString());
    
            if (uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
                try {
                    process(uri);
                    Log.e(TAG, "[Finish] general");
                } catch (Exception e) {
                    Log.e(TAG, "[Finish] error : " + e.toString(), e);
                }
            } else {
                Log.e(TAG, "[Finish] not EXTERNAL_CONTENT_URI ");
            }
        }
    
        public void register() {
            Log.d(TAG, "register");
            mContentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, this);
        }
    
        public void unregister() {
            Log.d(TAG, "unregister");
            mContentResolver.unregisterContentObserver(this);
        }
    
        private boolean process(Uri uri) throws Exception {
            Data result = getLatestData(uri);
            if (result == null) {
                Log.e(TAG, "[Result] result is null!!");
                return false;
            }
            if (lastPath != null && lastPath.equals(result.path)) {
                Log.e(TAG, "[Result] duplicate!!");
                return false;
            }
            long currentTime = System.currentTimeMillis() / 1000;
            if (matchPath(result.path) && matchTime(currentTime, result.dateAdded)) {
                lastPath = result.path;
                Uri screenUri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/" + result.id);
                Log.e(TAG, "[Result] This is screenshot!! : " + result.fileName + " | dateAdded : " + result.dateAdded + " / " + currentTime);
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContentResolver, screenUri);
                Bitmap copyBitmap = bitmap.copy(bitmap.getConfig(), true);
                bitmap.recycle();
                int temp = mContentResolver.delete(screenUri, null, null);
                Log.e(TAG, "Delete Result : " + temp);
                if (mListener != null) {
                    mListener.onScreenshotTaken(copyBitmap, result.fileName);
                }
                return true;
            } else {
                Log.e(TAG, "[Result] No ScreenShot : " + result.fileName);
            }
            return false;
        }
    
        private Data getLatestData(Uri uri) throws Exception {
            Data data = null;
            Cursor cursor = null;
            try {
                cursor = mContentResolver.query(uri, PROJECTION, null, null, SORT_ORDER);
                if (cursor != null && cursor.moveToFirst()) {
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID));
                    String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    long dateAdded = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
    
                    if (fileName.contains(FILE_POSTFIX)) {
                        if (cursor.moveToNext()) {
                            id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID));
                            fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
                            path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                            dateAdded = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
                        } else {
                            return null;
                        }
                    }
    
                    data = new Data();
                    data.id = id;
                    data.fileName = fileName;
                    data.path = path;
                    data.dateAdded = dateAdded;
                    Log.e(TAG, "[Recent File] Name : " + fileName);
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
            return data;
        }
    
        private boolean matchPath(String path) {
            return (path.toLowerCase().contains("screenshots/") && !path.contains(FILE_POSTFIX));
        }
    
        private boolean matchTime(long currentTime, long dateAdded) {
            return Math.abs(currentTime - dateAdded) <= DEFAULT_DETECT_WINDOW_SECONDS;
        }
    
        class Data {
            long id;
            String fileName;
            String path;
            long dateAdded;
        }
    }
    
    1. Util.class

      public static void saveImage(Context context, Bitmap bitmap, String title) throws Exception {
          OutputStream fOut = null;
          title = title.replaceAll(" ", "+");
          int index = title.lastIndexOf(".png");
          String fileName = title.substring(0, index) + ScreenShotContentObserver.FILE_POSTFIX + ".png";
          final String appDirectoryName = "Screenshots";
          final File imageRoot = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), appDirectoryName);
          imageRoot.mkdirs();
          final File file = new File(imageRoot, fileName);
          fOut = new FileOutputStream(file);
      
          bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
          fOut.flush();
          fOut.close();
      
          ContentValues values = new ContentValues();
          values.put(MediaStore.Images.Media.TITLE, "XXXXX");
          values.put(MediaStore.Images.Media.DESCRIPTION, "description here");
          values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
          values.put(MediaStore.Images.ImageColumns.BUCKET_ID, file.hashCode());
          values.put(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, file.getName());
          values.put("_data", file.getAbsolutePath());
          ContentResolver cr = context.getContentResolver();
          Uri newUri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
          context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
      }
      
    0 讨论(0)
提交回复
热议问题