2015-04-14 33 views
11

Tôi hiện đang phát triển một ứng dụng cho Android và muốn biết cách phát hiện ảnh chụp màn hình. Tôi đã thử với FileObserver nhưng vấn đề là tất cả các sự kiện được phát hiện (khi thiết bị đi vào giấc ngủ, tin nhắn, vv). Làm cách nào để phát hiện ảnh chụp màn hình?Chỉ phát hiện ảnh chụp màn hình với FileObserver Android

Cảm ơn bạn trước!

Trả lời

15

Bạn đã sử dụng FileObserver để phát hiện chế độ chụp màn hình như thế nào? Khi sử dụng FileObserver, chỉ giám sát sự kiện tạo tệp trong thư mục ảnh chụp màn hình.

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(); 

Đừng quên khai báo quyền tương ứng để truy cập nội dung trong thẻ SD.

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

Một giải pháp khác để phát hiện các ảnh chụp màn hình được sử dụng ContentObserver, vì sẽ có một kỷ lục chèn vào cơ sở dữ liệu hệ thống phương tiện truyền thông sau khi ảnh chụp màn hình. Sau đây là đoạn mã sử dụng ContentObserver để theo dõi sự kiện. Bằng cách sử dụng ContentObserver, bạn không cần khai báo các quyền của write/read external storage nhưng bạn phải thực hiện một số bộ lọc trên tên tệp để đảm bảo đó là sự kiện chụp màn hình.

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); 
       } 
      } 
    ); 

Cập nhật

Nếu bạn sử dụng phương pháp thứ hai, bạn phải yêu cầu READ_EXTERNAL_STORAGE sau khi phiên bản Android M, nếu không nó sẽ ném SecurityException. Để biết thêm thông tin về cách yêu cầu quyền thời gian chạy, hãy tham khảo here.

+1

'FileObserver' không làm việc cho tôi, nhưng cách hoạt động ContentProvider , mặc dù cần một số sửa đổi. Chúng ta cần nội dung hình ảnh truy vấn với thứ tự 'DESC', để có được hình ảnh mới nhất gây ra sự kiện' onChange' này. – Piasy

+0

ContentObserver cho ngoại lệ "java.lang.SecurityException: Permission từ chối: đọc com.android.providers.media.MediaProvider nội dung uri: // media/bên ngoài/images/phương tiện truyền thông từ pid = 31.855, uid = 10341 đòi hỏi android.permission. READ_EXTERNAL_STORAGE, hoặc grantUriPermission()", nếu Permission lưu trữ không được cấp tại MARSHMELLOW – shanraisshan

+0

@shanrais Có, bạn nên kiểm tra và yêu cầu sự cho phép thời gian chạy tại Android M. Hãy suy http://developer.android.com/training/permissions/ request.html để biết thêm thông tin. – alijandro

0

Tôi đã thực hiện dự án git cho ảnh chụp màn hình android.

Tôi đã sử dụng Trình theo dõi nội dung.

làm việc tốt từ API 14 lên phiên bản gần đây

Bạn có thể kiểm tra here



1.ScreenShotContentObserver .class
(ảnh chụp màn hình ban đầu xóa -> thông báo cho ảnh chụp màn hình chụp và đưa ảnh chụp màn hình 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)); 
    } 
    
1

Bạn có thể tạo FileObserver mà chỉ giám sát thư mục ảnh chụp màn hình cộng với chỉ kích hoạt sự kiện cho tập tin hoặc tạo thư mục. Để biết thêm thông tin, hãy nhấp vào here.

1

Tôi đã cải thiện mã từ nhận xét của alijandro để làm cho lớp dễ sử dụng và khắc phục sự cố khi người quan sát nội dung phát hiện hình ảnh từ máy ảnh (chỉ nên là ảnh chụp màn hình). Sau đó quấn nó vào lớp đại biểu để thuận tiện cho việc sử dụng.


• 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

Xin chào, Mã này làm việc cho tôi, nhưng có vấn đề trong một số thiết bị. Bạn có thể đưa ra một số giải pháp.? – bhoomika

+0

@bhoomika Bạn có bất kỳ thông tin nào về các thiết bị này không? – Akexorcist

Các vấn đề liên quan