Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 1 | package com.android.internal.util; |
| 2 | |
Phil Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 3 | import android.annotation.NonNull; |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 4 | import android.annotation.Nullable; |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 5 | import android.content.ComponentName; |
| 6 | import android.content.Context; |
| 7 | import android.content.Intent; |
| 8 | import android.content.ServiceConnection; |
| 9 | import android.os.Handler; |
| 10 | import android.os.IBinder; |
| 11 | import android.os.Message; |
| 12 | import android.os.Messenger; |
| 13 | import android.os.RemoteException; |
| 14 | import android.os.UserHandle; |
| 15 | import android.util.Log; |
| 16 | |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 17 | import java.util.function.Consumer; |
| 18 | |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 19 | public 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 Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 39 | /** |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 40 | * Request a screenshot be taken with a specific timeout. |
Phil Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 41 | * |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 42 | * 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 Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 58 | */ |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 59 | public void takeScreenshot(final int screenshotType, final boolean hasStatus, |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 60 | 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 Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 92 | 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'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 101 | @Override |
| 102 | public void run() { |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 103 | synchronized (mScreenshotLock) { |
| 104 | if (mScreenshotConnection != null) { |
| 105 | mContext.unbindService(mScreenshotConnection); |
| 106 | mScreenshotConnection = null; |
| 107 | notifyScreenshotError(); |
| 108 | } |
| 109 | } |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 110 | if (completionConsumer != null) { |
| 111 | completionConsumer.accept(false); |
| 112 | } |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 113 | } |
| 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'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 137 | if (completionConsumer != null) { |
| 138 | completionConsumer.accept(true); |
| 139 | } |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 140 | } |
| 141 | }; |
| 142 | msg.replyTo = new Messenger(h); |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 143 | msg.arg1 = hasStatus ? 1 : 0; |
| 144 | msg.arg2 = hasNav ? 1 : 0; |
| 145 | |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 146 | try { |
| 147 | messenger.send(msg); |
| 148 | } catch (RemoteException e) { |
| 149 | Log.e(TAG, "Couldn't take screenshot: " + e); |
James O'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 150 | if (completionConsumer != null) { |
| 151 | completionConsumer.accept(false); |
| 152 | } |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 153 | } |
| 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'Leary | fa5bb7a | 2019-09-05 13:43:29 -0400 | [diff] [blame] | 173 | handler.postDelayed(mScreenshotTimeout, timeoutMs); |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 174 | } |
| 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 | } |