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; |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 4 | import android.content.ComponentName; |
| 5 | import android.content.Context; |
| 6 | import android.content.Intent; |
| 7 | import android.content.ServiceConnection; |
| 8 | import android.os.Handler; |
| 9 | import android.os.IBinder; |
| 10 | import android.os.Message; |
| 11 | import android.os.Messenger; |
| 12 | import android.os.RemoteException; |
| 13 | import android.os.UserHandle; |
| 14 | import android.util.Log; |
| 15 | |
| 16 | public class ScreenshotHelper { |
| 17 | private static final String TAG = "ScreenshotHelper"; |
| 18 | |
| 19 | private static final String SYSUI_PACKAGE = "com.android.systemui"; |
| 20 | private static final String SYSUI_SCREENSHOT_SERVICE = |
| 21 | "com.android.systemui.screenshot.TakeScreenshotService"; |
| 22 | private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER = |
| 23 | "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver"; |
| 24 | |
| 25 | // Time until we give up on the screenshot & show an error instead. |
| 26 | private final int SCREENSHOT_TIMEOUT_MS = 10000; |
| 27 | |
| 28 | private final Object mScreenshotLock = new Object(); |
| 29 | private ServiceConnection mScreenshotConnection = null; |
| 30 | private final Context mContext; |
| 31 | |
| 32 | public ScreenshotHelper(Context context) { |
| 33 | mContext = context; |
| 34 | } |
| 35 | |
Phil Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 36 | /** |
| 37 | * Request a screenshot be taken. |
| 38 | * |
| 39 | * @param screenshotType The type of screenshot, for example either |
| 40 | * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN} |
| 41 | * or {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION} |
| 42 | * @param hasStatus {@code true} if the status bar is currently showing. {@code false} if not. |
| 43 | * @param hasNav {@code true} if the navigation bar is currently showing. {@code false} if not. |
| 44 | * @param handler A handler used in case the screenshot times out |
| 45 | */ |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 46 | public void takeScreenshot(final int screenshotType, final boolean hasStatus, |
Phil Weaver | d042974 | 2018-01-16 15:32:30 -0800 | [diff] [blame] | 47 | final boolean hasNav, @NonNull Handler handler) { |
Alison Cichowlas | a2cd19e | 2017-12-06 10:51:21 -0500 | [diff] [blame] | 48 | synchronized (mScreenshotLock) { |
| 49 | if (mScreenshotConnection != null) { |
| 50 | return; |
| 51 | } |
| 52 | final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, |
| 53 | SYSUI_SCREENSHOT_SERVICE); |
| 54 | final Intent serviceIntent = new Intent(); |
| 55 | |
| 56 | final Runnable mScreenshotTimeout = new Runnable() { |
| 57 | @Override public void run() { |
| 58 | synchronized (mScreenshotLock) { |
| 59 | if (mScreenshotConnection != null) { |
| 60 | mContext.unbindService(mScreenshotConnection); |
| 61 | mScreenshotConnection = null; |
| 62 | notifyScreenshotError(); |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | }; |
| 67 | |
| 68 | serviceIntent.setComponent(serviceComponent); |
| 69 | ServiceConnection conn = new ServiceConnection() { |
| 70 | @Override |
| 71 | public void onServiceConnected(ComponentName name, IBinder service) { |
| 72 | synchronized (mScreenshotLock) { |
| 73 | if (mScreenshotConnection != this) { |
| 74 | return; |
| 75 | } |
| 76 | Messenger messenger = new Messenger(service); |
| 77 | Message msg = Message.obtain(null, screenshotType); |
| 78 | final ServiceConnection myConn = this; |
| 79 | Handler h = new Handler(handler.getLooper()) { |
| 80 | @Override |
| 81 | public void handleMessage(Message msg) { |
| 82 | synchronized (mScreenshotLock) { |
| 83 | if (mScreenshotConnection == myConn) { |
| 84 | mContext.unbindService(mScreenshotConnection); |
| 85 | mScreenshotConnection = null; |
| 86 | handler.removeCallbacks(mScreenshotTimeout); |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | }; |
| 91 | msg.replyTo = new Messenger(h); |
| 92 | msg.arg1 = hasStatus ? 1: 0; |
| 93 | msg.arg2 = hasNav ? 1: 0; |
| 94 | try { |
| 95 | messenger.send(msg); |
| 96 | } catch (RemoteException e) { |
| 97 | Log.e(TAG, "Couldn't take screenshot: " + e); |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | @Override |
| 103 | public void onServiceDisconnected(ComponentName name) { |
| 104 | synchronized (mScreenshotLock) { |
| 105 | if (mScreenshotConnection != null) { |
| 106 | mContext.unbindService(mScreenshotConnection); |
| 107 | mScreenshotConnection = null; |
| 108 | handler.removeCallbacks(mScreenshotTimeout); |
| 109 | notifyScreenshotError(); |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | }; |
| 114 | if (mContext.bindServiceAsUser(serviceIntent, conn, |
| 115 | Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, |
| 116 | UserHandle.CURRENT)) { |
| 117 | mScreenshotConnection = conn; |
| 118 | handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Notifies the screenshot service to show an error. |
| 125 | */ |
| 126 | private void notifyScreenshotError() { |
| 127 | // If the service process is killed, then ask it to clean up after itself |
| 128 | final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE, |
| 129 | SYSUI_SCREENSHOT_ERROR_RECEIVER); |
| 130 | // Broadcast needs to have a valid action. We'll just pick |
| 131 | // a generic one, since the receiver here doesn't care. |
| 132 | Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT); |
| 133 | errorIntent.setComponent(errorComponent); |
| 134 | errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | |
| 135 | Intent.FLAG_RECEIVER_FOREGROUND); |
| 136 | mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); |
| 137 | } |
| 138 | |
| 139 | } |