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