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