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