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