| package com.android.internal.util; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.util.Log; |
| |
| import java.util.function.Consumer; |
| |
| public class ScreenshotHelper { |
| private static final String TAG = "ScreenshotHelper"; |
| |
| private static final String SYSUI_PACKAGE = "com.android.systemui"; |
| private static final String SYSUI_SCREENSHOT_SERVICE = |
| "com.android.systemui.screenshot.TakeScreenshotService"; |
| private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER = |
| "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver"; |
| |
| // Time until we give up on the screenshot & show an error instead. |
| private final int SCREENSHOT_TIMEOUT_MS = 10000; |
| |
| private final Object mScreenshotLock = new Object(); |
| private ServiceConnection mScreenshotConnection = null; |
| private final Context mContext; |
| |
| public ScreenshotHelper(Context context) { |
| mContext = context; |
| } |
| |
| /** |
| * Request a screenshot be taken with a specific timeout. |
| * |
| * Added to support reducing unit test duration; the method variant without a timeout argument |
| * is recommended for general use. |
| * |
| * @param screenshotType The type of screenshot, for example either |
| * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN} |
| * or |
| * {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION} |
| * @param hasStatus {@code true} if the status bar is currently showing. {@code false} |
| * if |
| * not. |
| * @param hasNav {@code true} if the navigation bar is currently showing. {@code |
| * false} |
| * if not. |
| * @param handler A handler used in case the screenshot times out |
| * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the |
| * screenshot was taken. |
| */ |
| public void takeScreenshot(final int screenshotType, final boolean hasStatus, |
| final boolean hasNav, @NonNull Handler handler, |
| @Nullable Consumer<Boolean> completionConsumer) { |
| takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler, |
| completionConsumer); |
| } |
| |
| /** |
| * Request a screenshot be taken. |
| * |
| * Added to support reducing unit test duration; the method variant without a timeout argument |
| * is recommended for general use. |
| * |
| * @param screenshotType The type of screenshot, for example either |
| * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN} |
| * or |
| * {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION} |
| * @param hasStatus {@code true} if the status bar is currently showing. {@code false} |
| * if |
| * not. |
| * @param hasNav {@code true} if the navigation bar is currently showing. {@code |
| * false} |
| * if not. |
| * @param timeoutMs If the screenshot hasn't been completed within this time period, |
| * the screenshot attempt will be cancelled and `completionConsumer` |
| * will be run. |
| * @param handler A handler used in case the screenshot times out |
| * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the |
| * screenshot was taken. |
| */ |
| public void takeScreenshot(final int screenshotType, final boolean hasStatus, |
| final boolean hasNav, long timeoutMs, @NonNull Handler handler, |
| @Nullable Consumer<Boolean> completionConsumer) { |
| synchronized (mScreenshotLock) { |
| if (mScreenshotConnection != null) { |
| return; |
| } |
| final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, |
| SYSUI_SCREENSHOT_SERVICE); |
| final Intent serviceIntent = new Intent(); |
| |
| final Runnable mScreenshotTimeout = new Runnable() { |
| @Override |
| public void run() { |
| synchronized (mScreenshotLock) { |
| if (mScreenshotConnection != null) { |
| mContext.unbindService(mScreenshotConnection); |
| mScreenshotConnection = null; |
| notifyScreenshotError(); |
| } |
| } |
| if (completionConsumer != null) { |
| completionConsumer.accept(false); |
| } |
| } |
| }; |
| |
| serviceIntent.setComponent(serviceComponent); |
| ServiceConnection conn = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| synchronized (mScreenshotLock) { |
| if (mScreenshotConnection != this) { |
| return; |
| } |
| Messenger messenger = new Messenger(service); |
| Message msg = Message.obtain(null, screenshotType); |
| final ServiceConnection myConn = this; |
| Handler h = new Handler(handler.getLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| synchronized (mScreenshotLock) { |
| if (mScreenshotConnection == myConn) { |
| mContext.unbindService(mScreenshotConnection); |
| mScreenshotConnection = null; |
| handler.removeCallbacks(mScreenshotTimeout); |
| } |
| } |
| if (completionConsumer != null) { |
| completionConsumer.accept(true); |
| } |
| } |
| }; |
| msg.replyTo = new Messenger(h); |
| msg.arg1 = hasStatus ? 1 : 0; |
| msg.arg2 = hasNav ? 1 : 0; |
| |
| try { |
| messenger.send(msg); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Couldn't take screenshot: " + e); |
| if (completionConsumer != null) { |
| completionConsumer.accept(false); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| synchronized (mScreenshotLock) { |
| if (mScreenshotConnection != null) { |
| mContext.unbindService(mScreenshotConnection); |
| mScreenshotConnection = null; |
| handler.removeCallbacks(mScreenshotTimeout); |
| notifyScreenshotError(); |
| } |
| } |
| } |
| }; |
| if (mContext.bindServiceAsUser(serviceIntent, conn, |
| Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, |
| UserHandle.CURRENT)) { |
| mScreenshotConnection = conn; |
| handler.postDelayed(mScreenshotTimeout, timeoutMs); |
| } |
| } |
| } |
| |
| /** |
| * Notifies the screenshot service to show an error. |
| */ |
| private void notifyScreenshotError() { |
| // If the service process is killed, then ask it to clean up after itself |
| final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE, |
| SYSUI_SCREENSHOT_ERROR_RECEIVER); |
| // Broadcast needs to have a valid action. We'll just pick |
| // a generic one, since the receiver here doesn't care. |
| Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT); |
| errorIntent.setComponent(errorComponent); |
| errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | |
| Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); |
| } |
| |
| } |