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