1package com.android.internal.util;
2
3import android.annotation.NonNull;
4import android.content.ComponentName;
5import android.content.Context;
6import android.content.Intent;
7import android.content.ServiceConnection;
8import android.os.Handler;
9import android.os.IBinder;
10import android.os.Message;
11import android.os.Messenger;
12import android.os.RemoteException;
13import android.os.UserHandle;
14import android.util.Log;
15
16public 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
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     */
46    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
47            final boolean hasNav, @NonNull Handler handler) {
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}
140