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         ComponentName topComponent = screenshotRequest.getTopComponent();
190         mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
191                 topComponent == null ? "" : topComponent.getPackageName());
192 
193         switch (msg.what) {
194             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
195                 if (DEBUG_SERVICE) {
196                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
197                 }
198                 mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);
199                 break;
200             case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
201                 if (DEBUG_SERVICE) {
202                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
203                 }
204                 mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);
205                 break;
206             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
207                 if (DEBUG_SERVICE) {
208                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
209                 }
210                 Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
211                         screenshotRequest.getBitmapBundle());
212                 Rect screenBounds = screenshotRequest.getBoundsInScreen();
213                 Insets insets = screenshotRequest.getInsets();
214                 int taskId = screenshotRequest.getTaskId();
215                 int userId = screenshotRequest.getUserId();
216 
217                 if (screenshot == null) {
218                     Log.e(TAG, "Got null bitmap from screenshot message");
219                     mNotificationsController.notifyScreenshotError(
220                             R.string.screenshot_failed_to_capture_text);
221                     requestCallback.reportError();
222                 } else {
223                     mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
224                             taskId, userId, topComponent, uriConsumer, requestCallback);
225                 }
226                 break;
227             default:
228                 Log.w(TAG, "Invalid screenshot option: " + msg.what);
229                 return false;
230         }
231         return true;
232     };
233 
sendComplete(Messenger target)234     private static void sendComplete(Messenger target) {
235         try {
236             if (DEBUG_CALLBACK) {
237                 Log.d(TAG, "sendComplete: " + target);
238             }
239             target.send(Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE));
240         } catch (RemoteException e) {
241             Log.d(TAG, "ignored remote exception", e);
242         }
243     }
244 
reportUri(Messenger target, Uri uri)245     private static void reportUri(Messenger target, Uri uri) {
246         try {
247             if (DEBUG_CALLBACK) {
248                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
249             }
250             target.send(Message.obtain(null, SCREENSHOT_MSG_URI, uri));
251         } catch (RemoteException e) {
252             Log.d(TAG, "ignored remote exception", e);
253         }
254     }
255 }
256