blob: cac691cf7d451f74d2983f1a58c624a222bc1325 [file] [log] [blame]
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -05001package com.android.internal.util;
2
Phil Weaverd0429742018-01-16 15:32:30 -08003import android.annotation.NonNull;
James O'Learyfa5bb7a2019-09-05 13:43:29 -04004import android.annotation.Nullable;
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -05005import android.content.ComponentName;
6import android.content.Context;
7import android.content.Intent;
8import android.content.ServiceConnection;
9import android.os.Handler;
10import android.os.IBinder;
11import android.os.Message;
12import android.os.Messenger;
13import android.os.RemoteException;
14import android.os.UserHandle;
15import android.util.Log;
16
James O'Learyfa5bb7a2019-09-05 13:43:29 -040017import java.util.function.Consumer;
18
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050019public class ScreenshotHelper {
20 private static final String TAG = "ScreenshotHelper";
21
22 private static final String SYSUI_PACKAGE = "com.android.systemui";
23 private static final String SYSUI_SCREENSHOT_SERVICE =
24 "com.android.systemui.screenshot.TakeScreenshotService";
25 private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
26 "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
27
28 // Time until we give up on the screenshot & show an error instead.
29 private final int SCREENSHOT_TIMEOUT_MS = 10000;
30
31 private final Object mScreenshotLock = new Object();
32 private ServiceConnection mScreenshotConnection = null;
33 private final Context mContext;
34
35 public ScreenshotHelper(Context context) {
36 mContext = context;
37 }
38
Phil Weaverd0429742018-01-16 15:32:30 -080039 /**
James O'Learyfa5bb7a2019-09-05 13:43:29 -040040 * Request a screenshot be taken with a specific timeout.
Phil Weaverd0429742018-01-16 15:32:30 -080041 *
James O'Learyfa5bb7a2019-09-05 13:43:29 -040042 * Added to support reducing unit test duration; the method variant without a timeout argument
43 * is recommended for general use.
44 *
45 * @param screenshotType The type of screenshot, for example either
46 * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
47 * or
48 * {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
49 * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
50 * if
51 * not.
52 * @param hasNav {@code true} if the navigation bar is currently showing. {@code
53 * false}
54 * if not.
55 * @param handler A handler used in case the screenshot times out
56 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
57 * screenshot was taken.
Phil Weaverd0429742018-01-16 15:32:30 -080058 */
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050059 public void takeScreenshot(final int screenshotType, final boolean hasStatus,
James O'Learyfa5bb7a2019-09-05 13:43:29 -040060 final boolean hasNav, @NonNull Handler handler,
61 @Nullable Consumer<Boolean> completionConsumer) {
62 takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
63 completionConsumer);
64 }
65
66 /**
67 * Request a screenshot be taken.
68 *
69 * Added to support reducing unit test duration; the method variant without a timeout argument
70 * is recommended for general use.
71 *
72 * @param screenshotType The type of screenshot, for example either
73 * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
74 * or
75 * {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
76 * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
77 * if
78 * not.
79 * @param hasNav {@code true} if the navigation bar is currently showing. {@code
80 * false}
81 * if not.
82 * @param timeoutMs If the screenshot hasn't been completed within this time period,
83 * the screenshot attempt will be cancelled and `completionConsumer`
84 * will be run.
85 * @param handler A handler used in case the screenshot times out
86 * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
87 * screenshot was taken.
88 */
89 public void takeScreenshot(final int screenshotType, final boolean hasStatus,
90 final boolean hasNav, long timeoutMs, @NonNull Handler handler,
91 @Nullable Consumer<Boolean> completionConsumer) {
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -050092 synchronized (mScreenshotLock) {
93 if (mScreenshotConnection != null) {
94 return;
95 }
96 final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
97 SYSUI_SCREENSHOT_SERVICE);
98 final Intent serviceIntent = new Intent();
99
100 final Runnable mScreenshotTimeout = new Runnable() {
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400101 @Override
102 public void run() {
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500103 synchronized (mScreenshotLock) {
104 if (mScreenshotConnection != null) {
105 mContext.unbindService(mScreenshotConnection);
106 mScreenshotConnection = null;
107 notifyScreenshotError();
108 }
109 }
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400110 if (completionConsumer != null) {
111 completionConsumer.accept(false);
112 }
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500113 }
114 };
115
116 serviceIntent.setComponent(serviceComponent);
117 ServiceConnection conn = new ServiceConnection() {
118 @Override
119 public void onServiceConnected(ComponentName name, IBinder service) {
120 synchronized (mScreenshotLock) {
121 if (mScreenshotConnection != this) {
122 return;
123 }
124 Messenger messenger = new Messenger(service);
125 Message msg = Message.obtain(null, screenshotType);
126 final ServiceConnection myConn = this;
127 Handler h = new Handler(handler.getLooper()) {
128 @Override
129 public void handleMessage(Message msg) {
130 synchronized (mScreenshotLock) {
131 if (mScreenshotConnection == myConn) {
132 mContext.unbindService(mScreenshotConnection);
133 mScreenshotConnection = null;
134 handler.removeCallbacks(mScreenshotTimeout);
135 }
136 }
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400137 if (completionConsumer != null) {
138 completionConsumer.accept(true);
139 }
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500140 }
141 };
142 msg.replyTo = new Messenger(h);
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400143 msg.arg1 = hasStatus ? 1 : 0;
144 msg.arg2 = hasNav ? 1 : 0;
145
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500146 try {
147 messenger.send(msg);
148 } catch (RemoteException e) {
149 Log.e(TAG, "Couldn't take screenshot: " + e);
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400150 if (completionConsumer != null) {
151 completionConsumer.accept(false);
152 }
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500153 }
154 }
155 }
156
157 @Override
158 public void onServiceDisconnected(ComponentName name) {
159 synchronized (mScreenshotLock) {
160 if (mScreenshotConnection != null) {
161 mContext.unbindService(mScreenshotConnection);
162 mScreenshotConnection = null;
163 handler.removeCallbacks(mScreenshotTimeout);
164 notifyScreenshotError();
165 }
166 }
167 }
168 };
169 if (mContext.bindServiceAsUser(serviceIntent, conn,
170 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
171 UserHandle.CURRENT)) {
172 mScreenshotConnection = conn;
James O'Learyfa5bb7a2019-09-05 13:43:29 -0400173 handler.postDelayed(mScreenshotTimeout, timeoutMs);
Alison Cichowlasa2cd19e2017-12-06 10:51:21 -0500174 }
175 }
176 }
177
178 /**
179 * Notifies the screenshot service to show an error.
180 */
181 private void notifyScreenshotError() {
182 // If the service process is killed, then ask it to clean up after itself
183 final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
184 SYSUI_SCREENSHOT_ERROR_RECEIVER);
185 // Broadcast needs to have a valid action. We'll just pick
186 // a generic one, since the receiver here doesn't care.
187 Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
188 errorIntent.setComponent(errorComponent);
189 errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
190 Intent.FLAG_RECEIVER_FOREGROUND);
191 mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
192 }
193
194}