1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.gecko; 7 8 import org.mozilla.gecko.AppConstants.Versions; 9 import org.mozilla.gecko.permissions.Permissions; 10 import org.mozilla.gecko.util.ThreadUtils; 11 12 import android.Manifest; 13 import android.content.ContentResolver; 14 import android.content.Context; 15 import android.database.ContentObserver; 16 import android.database.Cursor; 17 import android.net.Uri; 18 import android.provider.MediaStore; 19 import android.util.Log; 20 21 public class ScreenshotObserver { 22 private static final String LOGTAG = "GeckoScreenshotObserver"; 23 public Context context; 24 25 /** 26 * Listener for screenshot changes. 27 */ 28 public interface OnScreenshotListener { 29 /** 30 * This callback is executed on the UI thread. 31 */ 32 public void onScreenshotTaken(String data, String title); 33 } 34 35 private OnScreenshotListener listener; 36 37 public ScreenshotObserver() { 38 } 39 40 public void setListener(Context context, OnScreenshotListener listener) { 41 this.context = context; 42 this.listener = listener; 43 } 44 45 private MediaObserver mediaObserver; 46 private String[] mediaProjections = new String[] { 47 MediaStore.Images.ImageColumns.DATA, 48 MediaStore.Images.ImageColumns.DISPLAY_NAME, 49 MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, 50 MediaStore.Images.ImageColumns.DATE_TAKEN, 51 MediaStore.Images.ImageColumns.TITLE 52 }; 53 54 /** 55 * Start ScreenshotObserver if this device is supported and all required runtime permissions 56 * have been granted by the user. Calling this method will not prompt for permissions. 57 */ 58 public void start() { 59 Permissions.from(context) 60 .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) 61 .doNotPrompt() 62 .run(startObserverRunnable()); 63 } 64 65 private Runnable startObserverRunnable() { 66 return new Runnable() { 67 @Override 68 public void run() { 69 try { 70 if (mediaObserver == null) { 71 mediaObserver = new MediaObserver(); 72 context.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mediaObserver); 73 } 74 } catch (Exception e) { 75 Log.e(LOGTAG, "Failure to start watching media: ", e); 76 } 77 } 78 }; 79 } 80 81 public void stop() { 82 if (mediaObserver == null) { 83 return; 84 } 85 86 try { 87 context.getContentResolver().unregisterContentObserver(mediaObserver); 88 mediaObserver = null; 89 } catch (Exception e) { 90 Log.e(LOGTAG, "Failure to stop watching media: ", e); 91 } 92 } 93 94 public void onMediaChange(final Uri uri) { 95 // Make sure we are on not on the main thread. 96 final ContentResolver cr = context.getContentResolver(); 97 ThreadUtils.postToBackgroundThread(new Runnable() { 98 @Override 99 public void run() { 100 // Find the most recent image added to the MediaStore and see if it's a screenshot. 101 final Cursor cursor = cr.query(uri, mediaProjections, null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC LIMIT 1"); 102 try { 103 if (cursor == null) { 104 return; 105 } 106 107 while (cursor.moveToNext()) { 108 String data = cursor.getString(0); 109 Log.i(LOGTAG, "data: " + data); 110 String display = cursor.getString(1); 111 Log.i(LOGTAG, "display: " + display); 112 String album = cursor.getString(2); 113 Log.i(LOGTAG, "album: " + album); 114 long date = cursor.getLong(3); 115 String title = cursor.getString(4); 116 Log.i(LOGTAG, "title: " + title); 117 if (album != null && album.toLowerCase().contains("screenshot")) { 118 if (listener != null) { 119 listener.onScreenshotTaken(data, title); 120 break; 121 } 122 } 123 } 124 } catch (Exception e) { 125 Log.e(LOGTAG, "Failure to process media change: ", e); 126 } finally { 127 if (cursor != null) { 128 cursor.close(); 129 } 130 } 131 } 132 }); 133 } 134 135 private class MediaObserver extends ContentObserver { 136 public MediaObserver() { 137 super(null); 138 } 139 140 @Override 141 public void onChange(boolean selfChange) { 142 super.onChange(selfChange); 143 onMediaChange(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 144 } 145 } 146 } 147