• 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.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN;
20 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
21 
22 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
23 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
24 import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
25 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
26 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
27 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
28 import static com.android.systemui.screenshot.LogConfig.logTag;
29 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
30 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
31 
32 import android.annotation.MainThread;
33 import android.app.Service;
34 import android.app.admin.DevicePolicyManager;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.graphics.Bitmap;
41 import android.graphics.Insets;
42 import android.graphics.Rect;
43 import android.net.Uri;
44 import android.os.Handler;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.os.Messenger;
49 import android.os.RemoteException;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.util.Log;
53 import android.view.WindowManager;
54 import android.widget.Toast;
55 
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.internal.logging.UiEventLogger;
58 import com.android.internal.util.ScreenshotRequest;
59 import com.android.systemui.R;
60 import com.android.systemui.dagger.qualifiers.Background;
61 import com.android.systemui.flags.FeatureFlags;
62 import com.android.systemui.flags.FlagListenable.FlagEvent;
63 import com.android.systemui.flags.Flags;
64 
65 import java.util.concurrent.Executor;
66 import java.util.function.Consumer;
67 
68 import javax.inject.Inject;
69 
70 public class TakeScreenshotService extends Service {
71     private static final String TAG = logTag(TakeScreenshotService.class);
72 
73     private final ScreenshotController mScreenshot;
74 
75     private final UserManager mUserManager;
76     private final DevicePolicyManager mDevicePolicyManager;
77     private final UiEventLogger mUiEventLogger;
78     private final ScreenshotNotificationsController mNotificationsController;
79     private final Handler mHandler;
80     private final Context mContext;
81     private final @Background Executor mBgExecutor;
82     private final RequestProcessor mProcessor;
83     private final FeatureFlags mFeatureFlags;
84 
85     private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
86         @Override
87         public void onReceive(Context context, Intent intent) {
88             if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
89                 if (DEBUG_DISMISS) {
90                     Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
91                 }
92                 if (!mScreenshot.isPendingSharedTransition()) {
93                     mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
94                 }
95             }
96         }
97     };
98 
99     /** Informs about coarse grained state of the Controller. */
100     public interface RequestCallback {
101         /** Respond to the current request indicating the screenshot request failed. */
reportError()102         void reportError();
103 
104         /** The controller has completed handling this request UI has been removed */
onFinish()105         void onFinish();
106     }
107 
108     @Inject
TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController, Context context, @Background Executor bgExecutor, FeatureFlags featureFlags, RequestProcessor processor)109     public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
110             DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger,
111             ScreenshotNotificationsController notificationsController, Context context,
112             @Background Executor bgExecutor, FeatureFlags featureFlags,
113             RequestProcessor processor) {
114         if (DEBUG_SERVICE) {
115             Log.d(TAG, "new " + this);
116         }
117         mHandler = new Handler(Looper.getMainLooper(), this::handleMessage);
118         mScreenshot = screenshotController;
119         mUserManager = userManager;
120         mDevicePolicyManager = devicePolicyManager;
121         mUiEventLogger = uiEventLogger;
122         mNotificationsController = notificationsController;
123         mContext = context;
124         mBgExecutor = bgExecutor;
125         mFeatureFlags = featureFlags;
126         mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
127         mProcessor = processor;
128     }
129 
130     @Override
onCreate()131     public void onCreate() {
132         if (DEBUG_SERVICE) {
133             Log.d(TAG, "onCreate()");
134         }
135     }
136 
137     @Override
onBind(Intent intent)138     public IBinder onBind(Intent intent) {
139         registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS),
140                 Context.RECEIVER_EXPORTED);
141         final Messenger m = new Messenger(mHandler);
142         if (DEBUG_SERVICE) {
143             Log.d(TAG, "onBind: returning connection: " + m);
144         }
145         return m.getBinder();
146     }
147 
148     @Override
onUnbind(Intent intent)149     public boolean onUnbind(Intent intent) {
150         if (DEBUG_SERVICE) {
151             Log.d(TAG, "onUnbind");
152         }
153         mScreenshot.removeWindow();
154         unregisterReceiver(mCloseSystemDialogs);
155         return false;
156     }
157 
158     @Override
onDestroy()159     public void onDestroy() {
160         super.onDestroy();
161         mScreenshot.onDestroy();
162         if (DEBUG_SERVICE) {
163             Log.d(TAG, "onDestroy");
164         }
165     }
166 
167     static class RequestCallbackImpl implements RequestCallback {
168         private final Messenger mReplyTo;
169 
RequestCallbackImpl(Messenger replyTo)170         RequestCallbackImpl(Messenger replyTo) {
171             mReplyTo = replyTo;
172         }
173 
reportError()174         public void reportError() {
175             reportUri(mReplyTo, null);
176             sendComplete(mReplyTo);
177         }
178 
179         @Override
onFinish()180         public void onFinish() {
181             sendComplete(mReplyTo);
182         }
183     }
184 
185     @MainThread
handleMessage(Message msg)186     private boolean handleMessage(Message msg) {
187         final Messenger replyTo = msg.replyTo;
188         final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
189         RequestCallback callback = new RequestCallbackImpl(replyTo);
190 
191         ScreenshotRequest request = (ScreenshotRequest) msg.obj;
192 
193         handleRequest(request, onSaved, callback);
194         return true;
195     }
196 
197     @MainThread
198     @VisibleForTesting
handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved, RequestCallback callback)199     void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
200             RequestCallback callback) {
201         // If the storage for this user is locked, we have no place to store
202         // the screenshot, so skip taking it instead of showing a misleading
203         // animation and error notification.
204         if (!mUserManager.isUserUnlocked()) {
205             Log.w(TAG, "Skipping screenshot because storage is locked!");
206             logFailedRequest(request);
207             mNotificationsController.notifyScreenshotError(
208                     R.string.screenshot_failed_to_save_user_locked_text);
209             callback.reportError();
210             return;
211         }
212 
213         if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) {
214             mBgExecutor.execute(() -> {
215                 Log.w(TAG, "Skipping screenshot because an IT admin has disabled "
216                         + "screenshots on the device");
217                 logFailedRequest(request);
218                 String blockedByAdminText = mDevicePolicyManager.getResources().getString(
219                         SCREENSHOT_BLOCKED_BY_ADMIN,
220                         () -> mContext.getString(R.string.screenshot_blocked_by_admin));
221                 mHandler.post(() ->
222                         Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show());
223                 callback.reportError();
224             });
225             return;
226         }
227 
228         if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA_REFACTOR)) {
229             Log.d(TAG, "Processing screenshot data");
230             ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
231             try {
232                 mProcessor.processAsync(screenshotData,
233                         (data) -> dispatchToController(data, onSaved, callback));
234             } catch (IllegalStateException e) {
235                 Log.e(TAG, "Failed to process screenshot request!", e);
236                 logFailedRequest(request);
237                 mNotificationsController.notifyScreenshotError(
238                         R.string.screenshot_failed_to_capture_text);
239                 callback.reportError();
240             }
241         } else {
242             try {
243                 mProcessor.processAsync(request,
244                         (r) -> dispatchToController(r, onSaved, callback));
245             } catch (IllegalStateException e) {
246                 Log.e(TAG, "Failed to process screenshot request!", e);
247                 logFailedRequest(request);
248                 mNotificationsController.notifyScreenshotError(
249                         R.string.screenshot_failed_to_capture_text);
250                 callback.reportError();
251             }
252         }
253     }
254 
dispatchToController(ScreenshotData screenshot, Consumer<Uri> uriConsumer, RequestCallback callback)255     private void dispatchToController(ScreenshotData screenshot,
256             Consumer<Uri> uriConsumer, RequestCallback callback) {
257         mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
258                 screenshot.getPackageNameString());
259         mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
260     }
261 
dispatchToController(ScreenshotRequest request, Consumer<Uri> uriConsumer, RequestCallback callback)262     private void dispatchToController(ScreenshotRequest request,
263             Consumer<Uri> uriConsumer, RequestCallback callback) {
264         ComponentName topComponent = request.getTopComponent();
265         String packageName = topComponent == null ? "" : topComponent.getPackageName();
266         mUiEventLogger.log(
267                 ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
268 
269         switch (request.getType()) {
270             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
271                 if (DEBUG_SERVICE) {
272                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
273                 }
274                 mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
275                 break;
276             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
277                 if (DEBUG_SERVICE) {
278                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
279                 }
280                 Bitmap screenshot = request.getBitmap();
281                 Rect screenBounds = request.getBoundsInScreen();
282                 Insets insets = request.getInsets();
283                 int taskId = request.getTaskId();
284                 int userId = request.getUserId();
285 
286                 mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
287                         taskId, userId, topComponent, uriConsumer, callback);
288                 break;
289             default:
290                 Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
291         }
292     }
293 
logFailedRequest(ScreenshotRequest request)294     private void logFailedRequest(ScreenshotRequest request) {
295         ComponentName topComponent = request.getTopComponent();
296         String packageName = topComponent == null ? "" : topComponent.getPackageName();
297         mUiEventLogger.log(
298                 ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
299         mUiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, packageName);
300     }
301 
sendComplete(Messenger target)302     private static void sendComplete(Messenger target) {
303         try {
304             if (DEBUG_CALLBACK) {
305                 Log.d(TAG, "sendComplete: " + target);
306             }
307             target.send(Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE));
308         } catch (RemoteException e) {
309             Log.d(TAG, "ignored remote exception", e);
310         }
311     }
312 
reportUri(Messenger target, Uri uri)313     private static void reportUri(Messenger target, Uri uri) {
314         try {
315             if (DEBUG_CALLBACK) {
316                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
317             }
318             target.send(Message.obtain(null, SCREENSHOT_MSG_URI, uri));
319         } catch (RemoteException e) {
320             Log.d(TAG, "ignored remote exception", e);
321         }
322     }
323 }
324