blob: 9bf05135c4c5ccb7e859b5416402cc4855310742 [file] [log] [blame]
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -05001package com.android.internal.util;
2
Miranda Kephart7b2c3132020-03-27 09:54:14 -04003import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
4
Phil Weaverd0429742018-01-16 15:32:30 -08005import android.annotation.NonNull;
James O'Learyfa5bb7a2019-09-05 13:43:29 -04006import android.annotation.Nullable;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -05007import android.content.ComponentName;
8import android.content.Context;
9import android.content.Intent;
10import android.content.ServiceConnection;
Zak Cohenebd3b8b2020-01-17 11:16:00 -080011import android.graphics.Insets;
12import android.graphics.Rect;
Aran Inkeb565fc2019-11-15 15:52:31 -050013import android.net.Uri;
Zak Cohen4cfb5902020-06-02 16:34:15 -070014import android.os.Bundle;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050015import android.os.Handler;
16import android.os.IBinder;
17import android.os.Message;
18import android.os.Messenger;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040019import android.os.Parcel;
20import android.os.Parcelable;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050021import android.os.RemoteException;
22import android.os.UserHandle;
23import android.util.Log;
Zak Cohenebd3b8b2020-01-17 11:16:00 -080024import android.view.WindowManager;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050025
James O'Learyfa5bb7a2019-09-05 13:43:29 -040026import java.util.function.Consumer;
27
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050028public class ScreenshotHelper {
Miranda Kephart7b2c3132020-03-27 09:54:14 -040029
Miranda Kepharta4c79752020-04-24 15:23:58 -040030 public static final int SCREENSHOT_MSG_URI = 1;
31 public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
32
Miranda Kephart7b2c3132020-03-27 09:54:14 -040033 /**
34 * Describes a screenshot request (to make it easier to pass data through to the handler).
35 */
36 public static class ScreenshotRequest implements Parcelable {
37 private int mSource;
38 private boolean mHasStatusBar;
39 private boolean mHasNavBar;
Zak Cohen4cfb5902020-06-02 16:34:15 -070040 private Bundle mBitmapBundle;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040041 private Rect mBoundsInScreen;
42 private Insets mInsets;
43 private int mTaskId;
Zak Cohen4cfb5902020-06-02 16:34:15 -070044 private int mUserId;
45 private ComponentName mTopComponent;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040046
47 ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
48 mSource = source;
49 mHasStatusBar = hasStatus;
50 mHasNavBar = hasNav;
51 }
52
Zak Cohen4cfb5902020-06-02 16:34:15 -070053 ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
54 int taskId, int userId, ComponentName topComponent) {
Miranda Kephart7b2c3132020-03-27 09:54:14 -040055 mSource = source;
Zak Cohen4cfb5902020-06-02 16:34:15 -070056 mBitmapBundle = bitmapBundle;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040057 mBoundsInScreen = boundsInScreen;
58 mInsets = insets;
59 mTaskId = taskId;
Zak Cohen4cfb5902020-06-02 16:34:15 -070060 mUserId = userId;
61 mTopComponent = topComponent;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040062 }
63
64 ScreenshotRequest(Parcel in) {
65 mSource = in.readInt();
66 mHasStatusBar = in.readBoolean();
67 mHasNavBar = in.readBoolean();
Zak Cohen4cfb5902020-06-02 16:34:15 -070068
Miranda Kephart7b2c3132020-03-27 09:54:14 -040069 if (in.readInt() == 1) {
Zak Cohen4cfb5902020-06-02 16:34:15 -070070 mBitmapBundle = in.readBundle(getClass().getClassLoader());
Miranda Kephart7b2c3132020-03-27 09:54:14 -040071 mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
72 mInsets = in.readParcelable(Insets.class.getClassLoader());
73 mTaskId = in.readInt();
Zak Cohen4cfb5902020-06-02 16:34:15 -070074 mUserId = in.readInt();
75 mTopComponent = in.readParcelable(ComponentName.class.getClassLoader());
Miranda Kephart7b2c3132020-03-27 09:54:14 -040076 }
77 }
78
79 public int getSource() {
80 return mSource;
81 }
82
83 public boolean getHasStatusBar() {
84 return mHasStatusBar;
85 }
86
87 public boolean getHasNavBar() {
88 return mHasNavBar;
89 }
90
Zak Cohen4cfb5902020-06-02 16:34:15 -070091 public Bundle getBitmapBundle() {
92 return mBitmapBundle;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040093 }
94
95 public Rect getBoundsInScreen() {
96 return mBoundsInScreen;
97 }
98
99 public Insets getInsets() {
100 return mInsets;
101 }
102
103 public int getTaskId() {
104 return mTaskId;
105 }
106
Zak Cohen4cfb5902020-06-02 16:34:15 -0700107
108 public int getUserId() {
109 return mUserId;
110 }
111
112 public ComponentName getTopComponent() {
113 return mTopComponent;
114 }
115
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400116 @Override
117 public int describeContents() {
118 return 0;
119 }
120
121 @Override
122 public void writeToParcel(Parcel dest, int flags) {
123 dest.writeInt(mSource);
124 dest.writeBoolean(mHasStatusBar);
125 dest.writeBoolean(mHasNavBar);
Zak Cohen4cfb5902020-06-02 16:34:15 -0700126 if (mBitmapBundle == null) {
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400127 dest.writeInt(0);
128 } else {
129 dest.writeInt(1);
Zak Cohen4cfb5902020-06-02 16:34:15 -0700130 dest.writeBundle(mBitmapBundle);
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400131 dest.writeParcelable(mBoundsInScreen, 0);
132 dest.writeParcelable(mInsets, 0);
133 dest.writeInt(mTaskId);
Zak Cohen4cfb5902020-06-02 16:34:15 -0700134 dest.writeInt(mUserId);
135 dest.writeParcelable(mTopComponent, 0);
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400136 }
137 }
138
139 public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
140 new Parcelable.Creator<ScreenshotRequest>() {
141
142 @Override
143 public ScreenshotRequest createFromParcel(Parcel source) {
144 return new ScreenshotRequest(source);
145 }
146
147 @Override
148 public ScreenshotRequest[] newArray(int size) {
149 return new ScreenshotRequest[size];
150 }
151 };
152 }
Miranda Kephartcc92b9c2020-05-11 14:42:57 -0400153
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500154 private static final String TAG = "ScreenshotHelper";
155
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500156 // Time until we give up on the screenshot & show an error instead.
157 private final int SCREENSHOT_TIMEOUT_MS = 10000;
158
159 private final Object mScreenshotLock = new Object();
Miranda Kepharta4c79752020-04-24 15:23:58 -0400160 private IBinder mScreenshotService = null;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500161 private ServiceConnection mScreenshotConnection = null;
162 private final Context mContext;
163
164 public ScreenshotHelper(Context context) {
165 mContext = context;
166 }
167
Phil Weaverd0429742018-01-16 15:32:30 -0800168 /**
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400169 * Request a screenshot be taken.
Phil Weaverd0429742018-01-16 15:32:30 -0800170 *
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400171 * Added to support reducing unit test duration; the method variant without a timeout argument
172 * is recommended for general use.
173 *
174 * @param screenshotType The type of screenshot, for example either
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800175 * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400176 * or
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800177 * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400178 * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400179 * if not.
180 * @param hasNav {@code true} if the navigation bar is currently showing. {@code
181 * false} if not.
182 * @param source The source of the screenshot request. One of
183 * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
184 * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
185 * @param handler A handler used in case the screenshot times out
186 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
187 * screenshot was taken.
188 */
189 public void takeScreenshot(final int screenshotType, final boolean hasStatus,
190 final boolean hasNav, int source, @NonNull Handler handler,
191 @Nullable Consumer<Uri> completionConsumer) {
192 ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
193 takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
194 completionConsumer);
195 }
196
197 /**
198 * Request a screenshot be taken, with provided reason.
199 *
200 * @param screenshotType The type of screenshot, for example either
201 * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
202 * or
203 * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
204 * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400205 * if
206 * not.
207 * @param hasNav {@code true} if the navigation bar is currently showing. {@code
208 * false}
209 * if not.
210 * @param handler A handler used in case the screenshot times out
211 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
212 * screenshot was taken.
Phil Weaverd0429742018-01-16 15:32:30 -0800213 */
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500214 public void takeScreenshot(final int screenshotType, final boolean hasStatus,
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400215 final boolean hasNav, @NonNull Handler handler,
Aran Inkeb565fc2019-11-15 15:52:31 -0500216 @Nullable Consumer<Uri> completionConsumer) {
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400217 takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
218 completionConsumer);
219 }
220
221 /**
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400222 * Request a screenshot be taken with a specific timeout.
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400223 *
224 * Added to support reducing unit test duration; the method variant without a timeout argument
225 * is recommended for general use.
226 *
227 * @param screenshotType The type of screenshot, for example either
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800228 * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400229 * or
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800230 * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400231 * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
232 * if
233 * not.
234 * @param hasNav {@code true} if the navigation bar is currently showing. {@code
235 * false}
236 * if not.
237 * @param timeoutMs If the screenshot hasn't been completed within this time period,
238 * the screenshot attempt will be cancelled and `completionConsumer`
239 * will be run.
240 * @param handler A handler used in case the screenshot times out
241 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
242 * screenshot was taken.
243 */
244 public void takeScreenshot(final int screenshotType, final boolean hasStatus,
245 final boolean hasNav, long timeoutMs, @NonNull Handler handler,
Aran Inkeb565fc2019-11-15 15:52:31 -0500246 @Nullable Consumer<Uri> completionConsumer) {
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400247 ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
248 hasNav);
249 takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800250 }
251
252 /**
253 * Request that provided image be handled as if it was a screenshot.
254 *
Zak Cohen4cfb5902020-06-02 16:34:15 -0700255 * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800256 * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from.
257 * @param insets The insets that the image was shown with, inside the screenbounds.
258 * @param taskId The taskId of the task that the screen shot was taken of.
Zak Cohen4cfb5902020-06-02 16:34:15 -0700259 * @param userId The userId of user running the task provided in taskId.
260 * @param topComponent The component name of the top component running in the task.
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800261 * @param handler A handler used in case the screenshot times out
262 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
263 * screenshot was taken.
264 */
Zak Cohen4cfb5902020-06-02 16:34:15 -0700265 public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
266 @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400267 @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
268 ScreenshotRequest screenshotRequest =
Zak Cohen4cfb5902020-06-02 16:34:15 -0700269 new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
270 userId, topComponent);
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400271 takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
272 handler, screenshotRequest, completionConsumer);
Zak Cohenebd3b8b2020-01-17 11:16:00 -0800273 }
274
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400275 private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
276 ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500277 synchronized (mScreenshotLock) {
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500278
Miranda Kepharta4c79752020-04-24 15:23:58 -0400279 final Runnable mScreenshotTimeout = () -> {
280 synchronized (mScreenshotLock) {
281 if (mScreenshotConnection != null) {
282 mContext.unbindService(mScreenshotConnection);
283 mScreenshotConnection = null;
284 mScreenshotService = null;
285 notifyScreenshotError();
286 }
287 }
288 if (completionConsumer != null) {
289 completionConsumer.accept(null);
290 }
291 };
292
293 Message msg = Message.obtain(null, screenshotType, screenshotRequest);
294 final ServiceConnection myConn = mScreenshotConnection;
295 Handler h = new Handler(handler.getLooper()) {
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400296 @Override
Miranda Kepharta4c79752020-04-24 15:23:58 -0400297 public void handleMessage(Message msg) {
298 switch (msg.what) {
299 case SCREENSHOT_MSG_URI:
300 if (completionConsumer != null) {
301 completionConsumer.accept((Uri) msg.obj);
302 }
303 handler.removeCallbacks(mScreenshotTimeout);
304 break;
305 case SCREENSHOT_MSG_PROCESS_COMPLETE:
306 synchronized (mScreenshotLock) {
Miranda Kephartcc92b9c2020-05-11 14:42:57 -0400307 if (myConn != null && mScreenshotConnection == myConn) {
308 mContext.unbindService(myConn);
Miranda Kepharta4c79752020-04-24 15:23:58 -0400309 mScreenshotConnection = null;
310 mScreenshotService = null;
311 }
312 }
313 break;
314 }
315 }
316 };
317 msg.replyTo = new Messenger(h);
318
Miranda Kephart33aff2622020-06-12 13:04:18 -0400319 if (mScreenshotConnection == null || mScreenshotService == null) {
Miranda Kepharta4c79752020-04-24 15:23:58 -0400320 final ComponentName serviceComponent = ComponentName.unflattenFromString(
321 mContext.getResources().getString(
322 com.android.internal.R.string.config_screenshotServiceComponent));
323 final Intent serviceIntent = new Intent();
324
325 serviceIntent.setComponent(serviceComponent);
326 ServiceConnection conn = new ServiceConnection() {
327 @Override
328 public void onServiceConnected(ComponentName name, IBinder service) {
329 synchronized (mScreenshotLock) {
330 if (mScreenshotConnection != this) {
331 return;
332 }
333 mScreenshotService = service;
334 Messenger messenger = new Messenger(mScreenshotService);
335
336 try {
337 messenger.send(msg);
338 } catch (RemoteException e) {
339 Log.e(TAG, "Couldn't take screenshot: " + e);
340 if (completionConsumer != null) {
341 completionConsumer.accept(null);
342 }
343 }
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500344 }
345 }
Miranda Kepharta4c79752020-04-24 15:23:58 -0400346
347 @Override
348 public void onServiceDisconnected(ComponentName name) {
349 synchronized (mScreenshotLock) {
350 if (mScreenshotConnection != null) {
351 mContext.unbindService(mScreenshotConnection);
352 mScreenshotConnection = null;
353 mScreenshotService = null;
Miranda Kephart700a0932020-06-04 15:59:35 -0400354 // only log an error if we're still within the timeout period
355 if (handler.hasCallbacks(mScreenshotTimeout)) {
356 handler.removeCallbacks(mScreenshotTimeout);
357 notifyScreenshotError();
358 }
Miranda Kepharta4c79752020-04-24 15:23:58 -0400359 }
360 }
361 }
362 };
363 if (mContext.bindServiceAsUser(serviceIntent, conn,
364 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
365 UserHandle.CURRENT)) {
366 mScreenshotConnection = conn;
367 handler.postDelayed(mScreenshotTimeout, timeoutMs);
368 }
369 } else {
370 Messenger messenger = new Messenger(mScreenshotService);
371 try {
372 messenger.send(msg);
373 } catch (RemoteException e) {
374 Log.e(TAG, "Couldn't take screenshot: " + e);
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400375 if (completionConsumer != null) {
Aran Inkeb565fc2019-11-15 15:52:31 -0500376 completionConsumer.accept(null);
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400377 }
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500378 }
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400379 handler.postDelayed(mScreenshotTimeout, timeoutMs);
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500380 }
381 }
382 }
383
384 /**
385 * Notifies the screenshot service to show an error.
386 */
387 private void notifyScreenshotError() {
388 // If the service process is killed, then ask it to clean up after itself
Winson Chungf2b41772019-11-06 15:00:48 -0800389 final ComponentName errorComponent = ComponentName.unflattenFromString(
390 mContext.getResources().getString(
391 com.android.internal.R.string.config_screenshotErrorReceiverComponent));
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500392 // Broadcast needs to have a valid action. We'll just pick
393 // a generic one, since the receiver here doesn't care.
394 Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
395 errorIntent.setComponent(errorComponent);
396 errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
397 Intent.FLAG_RECEIVER_FOREGROUND);
398 mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
399 }
400
401}