• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenshot;
18 
19 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
20 
21 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
22 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
23 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
24 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
25 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
26 import static com.android.systemui.screenshot.LogConfig.logTag;
27 
28 import android.annotation.MainThread;
29 import android.app.Service;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.graphics.Bitmap;
36 import android.graphics.Insets;
37 import android.graphics.Rect;
38 import android.net.Uri;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.os.Messenger;
44 import android.os.RemoteException;
45 import android.os.UserManager;
46 import android.util.Log;
47 import android.view.WindowManager;
48 
49 import androidx.annotation.NonNull;
50 
51 import com.android.internal.logging.UiEventLogger;
52 import com.android.internal.util.ScreenshotHelper;
53 import com.android.systemui.R;
54 import com.android.systemui.shared.recents.utilities.BitmapUtil;
55 
56 import java.util.function.Consumer;
57 
58 import javax.inject.Inject;
59 
60 public class TakeScreenshotService extends Service {
61     private static final String TAG = logTag(TakeScreenshotService.class);
62 
63     private ScreenshotController mScreenshot;
64 
65     private final UserManager mUserManager;
66     private final UiEventLogger mUiEventLogger;
67     private final ScreenshotNotificationsController mNotificationsController;
68     private final Handler mHandler;
69 
70     private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
71         @Override
72         public void onReceive(Context context, Intent intent) {
73             if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
74                 if (DEBUG_DISMISS) {
75                     Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
76                 }
77                 if (!mScreenshot.isPendingSharedTransition()) {
78                     mScreenshot.dismissScreenshot(false);
79                 }
80             }
81         }
82     };
83 
84     /** Informs about coarse grained state of the Controller. */
85     interface RequestCallback {
86         /** Respond to the current request indicating the screenshot request failed.*/
reportError()87         void reportError();
88 
89         /** The controller has completed handling this request UI has been removed */
onFinish()90         void onFinish();
91     }
92 
93     @Inject
TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController)94     public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
95             UiEventLogger uiEventLogger,
96             ScreenshotNotificationsController notificationsController) {
97         if (DEBUG_SERVICE) {
98             Log.d(TAG, "new " + this);
99         }
100         mHandler = new Handler(Looper.getMainLooper(), this::handleMessage);
101         mScreenshot = screenshotController;
102         mUserManager = userManager;
103         mUiEventLogger = uiEventLogger;
104         mNotificationsController = notificationsController;
105     }
106 
107     @Override
onCreate()108     public void onCreate() {
109         if (DEBUG_SERVICE) {
110             Log.d(TAG, "onCreate()");
111         }
112     }
113 
114     @Override
onBind(@onNull Intent intent)115     public IBinder onBind(@NonNull Intent intent) {
116         registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
117         final Messenger m = new Messenger(mHandler);
118         if (DEBUG_SERVICE) {
119             Log.d(TAG, "onBind: returning connection: " + m);
120         }
121         return m.getBinder();
122     }
123 
124     @Override
onUnbind(Intent intent)125     public boolean onUnbind(Intent intent) {
126         if (DEBUG_SERVICE) {
127             Log.d(TAG, "onUnbind");
128         }
129         if (mScreenshot != null) {
130             mScreenshot.removeWindow();
131             mScreenshot = null;
132         }
133         unregisterReceiver(mCloseSystemDialogs);
134         return false;
135     }
136 
137     @Override
onDestroy()138     public void onDestroy() {
139         super.onDestroy();
140         if (mScreenshot != null) {
141             mScreenshot.removeWindow();
142             mScreenshot.releaseContext();
143             mScreenshot = null;
144         }
145         if (DEBUG_SERVICE) {
146             Log.d(TAG, "onDestroy");
147         }
148     }
149 
150     static class RequestCallbackImpl implements RequestCallback {
151         private final Messenger mReplyTo;
152 
RequestCallbackImpl(Messenger replyTo)153         RequestCallbackImpl(Messenger replyTo) {
154             mReplyTo = replyTo;
155         }
156 
reportError()157         public void reportError() {
158             reportUri(mReplyTo, null);
159             sendComplete(mReplyTo);
160         }
161 
162         @Override
onFinish()163         public void onFinish() {
164             sendComplete(mReplyTo);
165         }
166     }
167 
168     /** Respond to incoming Message via Binder (Messenger) */
169     @MainThread
handleMessage(Message msg)170     private boolean handleMessage(Message msg) {
171         final Messenger replyTo = msg.replyTo;
172         final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);
173         RequestCallback requestCallback = new RequestCallbackImpl(replyTo);
174 
175         // If the storage for this user is locked, we have no place to store
176         // the screenshot, so skip taking it instead of showing a misleading
177         // animation and error notification.
178         if (!mUserManager.isUserUnlocked()) {
179             Log.w(TAG, "Skipping screenshot because storage is locked!");
180             mNotificationsController.notifyScreenshotError(
181                     R.string.screenshot_failed_to_save_user_locked_text);
182             requestCallback.reportError();
183             return true;
184         }
185 
186         ScreenshotHelper.ScreenshotRequest screenshotRequest =
187                 (ScreenshotHelper.ScreenshotRequest) msg.obj;
188 
189         mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
190 
191         switch (msg.what) {
192             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
193                 if (DEBUG_SERVICE) {
194                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
195                 }
196                 mScreenshot.takeScreenshotFullscreen(uriConsumer, requestCallback);
197                 break;
198             case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
199                 if (DEBUG_SERVICE) {
200                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
201                 }
202                 mScreenshot.takeScreenshotPartial(uriConsumer, requestCallback);
203                 break;
204             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
205                 if (DEBUG_SERVICE) {
206                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
207                 }
208                 Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
209                         screenshotRequest.getBitmapBundle());
210                 Rect screenBounds = screenshotRequest.getBoundsInScreen();
211                 Insets insets = screenshotRequest.getInsets();
212                 int taskId = screenshotRequest.getTaskId();
213                 int userId = screenshotRequest.getUserId();
214                 ComponentName topComponent = screenshotRequest.getTopComponent();
215 
216                 if (screenshot == null) {
217                     Log.e(TAG, "Got null bitmap from screenshot message");
218                     mNotificationsController.notifyScreenshotError(
219                             R.string.screenshot_failed_to_capture_text);
220                     requestCallback.reportError();
221                 } else {
222                     mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
223                             taskId, userId, topComponent, uriConsumer, requestCallback);
224                 }
225                 break;
226             default:
227                 Log.w(TAG, "Invalid screenshot option: " + msg.what);
228                 return false;
229         }
230         return true;
231     };
232 
sendComplete(Messenger target)233     private static void sendComplete(Messenger target) {
234         try {
235             if (DEBUG_CALLBACK) {
236                 Log.d(TAG, "sendComplete: " + target);
237             }
238             target.send(Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE));
239         } catch (RemoteException e) {
240             Log.d(TAG, "ignored remote exception", e);
241         }
242     }
243 
reportUri(Messenger target, Uri uri)244     private static void reportUri(Messenger target, Uri uri) {
245         try {
246             if (DEBUG_CALLBACK) {
247                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
248             }
249             target.send(Message.obtain(null, SCREENSHOT_MSG_URI, uri));
250         } catch (RemoteException e) {
251             Log.d(TAG, "ignored remote exception", e);
252         }
253     }
254 }
255