• 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.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 
30 import android.annotation.MainThread;
31 import android.app.Service;
32 import android.app.admin.DevicePolicyManager;
33 import android.content.BroadcastReceiver;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
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.UserHandle;
46 import android.os.UserManager;
47 import android.util.Log;
48 import android.view.Display;
49 import android.widget.Toast;
50 
51 import com.android.internal.logging.UiEventLogger;
52 import com.android.internal.util.ScreenshotRequest;
53 import com.android.systemui.dagger.qualifiers.Background;
54 import com.android.systemui.res.R;
55 
56 import java.util.concurrent.Executor;
57 import java.util.function.Consumer;
58 
59 import javax.inject.Inject;
60 
61 public class TakeScreenshotService extends Service {
62     private static final String TAG = logTag(TakeScreenshotService.class);
63 
64     private final UserManager mUserManager;
65     private final DevicePolicyManager mDevicePolicyManager;
66     private final UiEventLogger mUiEventLogger;
67     private final ScreenshotNotificationsController mNotificationsController;
68     private final Handler mHandler;
69     private final Context mContext;
70     private final @Background Executor mBgExecutor;
71     private final TakeScreenshotExecutor mTakeScreenshotExecutor;
72 
73     @SuppressWarnings("deprecation")
74     private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
75         @Override
76         public void onReceive(Context context, Intent intent) {
77             if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
78                 if (DEBUG_DISMISS) {
79                     Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
80                 }
81                 mTakeScreenshotExecutor.onCloseSystemDialogsReceived();
82             }
83         }
84     };
85 
86     /** Informs about coarse grained state of the Controller. */
87     public interface RequestCallback {
88         /**
89          * Respond to the current request indicating the screenshot request failed.
90          * <p>
91          * After this, the service will be disconnected and all visible UI is removed.
92          */
reportError()93         void reportError();
94 
95         /** The controller has completed handling this request UI has been removed */
onFinish()96         void onFinish();
97     }
98 
99     @Inject
TakeScreenshotService( UserManager userManager, DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, ScreenshotNotificationsController.Factory notificationsControllerFactory, Context context, @Background Executor bgExecutor, TakeScreenshotExecutor takeScreenshotExecutor)100     public TakeScreenshotService(
101             UserManager userManager,
102             DevicePolicyManager devicePolicyManager,
103             UiEventLogger uiEventLogger,
104             ScreenshotNotificationsController.Factory notificationsControllerFactory,
105             Context context,
106             @Background Executor bgExecutor,
107             TakeScreenshotExecutor takeScreenshotExecutor) {
108         if (DEBUG_SERVICE) {
109             Log.d(TAG, "new " + this);
110         }
111         mHandler = new Handler(Looper.getMainLooper(), this::handleMessage);
112         mUserManager = userManager;
113         mDevicePolicyManager = devicePolicyManager;
114         mUiEventLogger = uiEventLogger;
115         mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
116         mContext = context;
117         mBgExecutor = bgExecutor;
118         mTakeScreenshotExecutor = takeScreenshotExecutor;
119     }
120 
121     @Override
onCreate()122     public void onCreate() {
123         if (DEBUG_SERVICE) {
124             Log.d(TAG, "onCreate()");
125         }
126     }
127 
128     @Override
onBind(Intent intent)129     public IBinder onBind(Intent intent) {
130         registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS),
131                 Context.RECEIVER_EXPORTED);
132         final Messenger m = new Messenger(mHandler);
133         if (DEBUG_SERVICE) {
134             Log.d(TAG, "onBind: returning connection: " + m);
135         }
136         return m.getBinder();
137     }
138 
139     @Override
onUnbind(Intent intent)140     public boolean onUnbind(Intent intent) {
141         if (DEBUG_SERVICE) {
142             Log.d(TAG, "onUnbind");
143         }
144         mTakeScreenshotExecutor.removeWindows();
145         unregisterReceiver(mCloseSystemDialogs);
146         return false;
147     }
148 
149     @Override
onDestroy()150     public void onDestroy() {
151         super.onDestroy();
152         mTakeScreenshotExecutor.onDestroy();
153         if (DEBUG_SERVICE) {
154             Log.d(TAG, "onDestroy");
155         }
156     }
157 
158     static class RequestCallbackImpl implements RequestCallback {
159         private final Messenger mReplyTo;
160 
RequestCallbackImpl(Messenger replyTo)161         RequestCallbackImpl(Messenger replyTo) {
162             mReplyTo = replyTo;
163         }
164 
165         @Override
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
handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved, RequestCallback callback)190     void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
191             RequestCallback callback) {
192         // If the storage for this user is locked, we have no place to store
193         // the screenshot, so skip taking it instead of showing a misleading
194         // animation and error notification.
195         if (!mUserManager.isUserUnlocked()) {
196             Log.w(TAG, "Skipping screenshot because storage is locked!");
197             logFailedRequest(request);
198             mNotificationsController.notifyScreenshotError(
199                     R.string.screenshot_failed_to_save_user_locked_text);
200             callback.reportError();
201             return;
202         }
203 
204         if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) {
205             mBgExecutor.execute(() -> {
206                 Log.w(TAG, "Skipping screenshot because an IT admin has disabled "
207                         + "screenshots on the device");
208                 logFailedRequest(request);
209                 String blockedByAdminText = mDevicePolicyManager.getResources().getString(
210                         SCREENSHOT_BLOCKED_BY_ADMIN,
211                         () -> mContext.getString(R.string.screenshot_blocked_by_admin));
212                 mHandler.post(() ->
213                         Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show());
214                 callback.reportError();
215             });
216             return;
217         }
218 
219         Log.d(TAG, "Processing screenshot data");
220         mTakeScreenshotExecutor.executeScreenshotsAsync(request, onSaved, callback);
221     }
222 
223 
logFailedRequest(ScreenshotRequest request)224     private void logFailedRequest(ScreenshotRequest request) {
225         ComponentName topComponent = request.getTopComponent();
226         String packageName = topComponent == null ? "" : topComponent.getPackageName();
227         mUiEventLogger.log(
228                 ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
229         mUiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, packageName);
230     }
231 
sendComplete(Messenger target)232     private static void sendComplete(Messenger target) {
233         try {
234             if (DEBUG_CALLBACK) {
235                 Log.d(TAG, "sendComplete: " + target);
236             }
237             target.send(Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE));
238         } catch (RemoteException e) {
239             Log.d(TAG, "ignored remote exception", e);
240         }
241     }
242 
reportUri(Messenger target, Uri uri)243     private static void reportUri(Messenger target, Uri uri) {
244         try {
245             if (DEBUG_CALLBACK) {
246                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
247             }
248             target.send(Message.obtain(null, SCREENSHOT_MSG_URI, uri));
249         } catch (RemoteException e) {
250             Log.d(TAG, "ignored remote exception", e);
251         }
252     }
253 }
254