1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
19import android.accessibilityservice.AccessibilityServiceInfo;
20import android.accessibilityservice.IAccessibilityServiceClient;
21import android.content.Context;
22import android.graphics.Bitmap;
23import android.hardware.input.InputManager;
24import android.os.Binder;
25import android.os.IBinder;
26import android.os.ParcelFileDescriptor;
27import android.os.Process;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.view.IWindowManager;
31import android.view.InputEvent;
32import android.view.SurfaceControl;
33import android.view.WindowAnimationFrameStats;
34import android.view.WindowContentFrameStats;
35import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.IAccessibilityManager;
37import libcore.io.IoUtils;
38
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.OutputStream;
43
44/**
45 * This is a remote object that is passed from the shell to an instrumentation
46 * for enabling access to privileged operations which the shell can do and the
47 * instrumentation cannot. These privileged operations are needed for implementing
48 * a {@link UiAutomation} that enables across application testing by simulating
49 * user actions and performing screen introspection.
50 *
51 * @hide
52 */
53public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
54
55    private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
56
57    private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
58            ServiceManager.getService(Service.WINDOW_SERVICE));
59
60    private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
61            .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
62
63    private final Object mLock = new Object();
64
65    private final Binder mToken = new Binder();
66
67    private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
68
69    private IAccessibilityServiceClient mClient;
70
71    private boolean mIsShutdown;
72
73    private int mOwningUid;
74
75    public void connect(IAccessibilityServiceClient client) {
76        if (client == null) {
77            throw new IllegalArgumentException("Client cannot be null!");
78        }
79        synchronized (mLock) {
80            throwIfShutdownLocked();
81            if (isConnectedLocked()) {
82                throw new IllegalStateException("Already connected.");
83            }
84            mOwningUid = Binder.getCallingUid();
85            registerUiTestAutomationServiceLocked(client);
86            storeRotationStateLocked();
87        }
88    }
89
90    @Override
91    public void disconnect() {
92        synchronized (mLock) {
93            throwIfCalledByNotTrustedUidLocked();
94            throwIfShutdownLocked();
95            if (!isConnectedLocked()) {
96                throw new IllegalStateException("Already disconnected.");
97            }
98            mOwningUid = -1;
99            unregisterUiTestAutomationServiceLocked();
100            restoreRotationStateLocked();
101        }
102    }
103
104    @Override
105    public boolean injectInputEvent(InputEvent event, boolean sync) {
106        synchronized (mLock) {
107            throwIfCalledByNotTrustedUidLocked();
108            throwIfShutdownLocked();
109            throwIfNotConnectedLocked();
110        }
111        final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
112                : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
113        final long identity = Binder.clearCallingIdentity();
114        try {
115            return InputManager.getInstance().injectInputEvent(event, mode);
116        } finally {
117            Binder.restoreCallingIdentity(identity);
118        }
119    }
120
121    @Override
122    public boolean setRotation(int rotation) {
123        synchronized (mLock) {
124            throwIfCalledByNotTrustedUidLocked();
125            throwIfShutdownLocked();
126            throwIfNotConnectedLocked();
127        }
128        final long identity = Binder.clearCallingIdentity();
129        try {
130            if (rotation == UiAutomation.ROTATION_UNFREEZE) {
131                mWindowManager.thawRotation();
132            } else {
133                mWindowManager.freezeRotation(rotation);
134            }
135            return true;
136        } catch (RemoteException re) {
137            /* ignore */
138        } finally {
139            Binder.restoreCallingIdentity(identity);
140        }
141        return false;
142    }
143
144    @Override
145    public Bitmap takeScreenshot(int width, int height) {
146        synchronized (mLock) {
147            throwIfCalledByNotTrustedUidLocked();
148            throwIfShutdownLocked();
149            throwIfNotConnectedLocked();
150        }
151        final long identity = Binder.clearCallingIdentity();
152        try {
153            return SurfaceControl.screenshot(width, height);
154        } finally {
155            Binder.restoreCallingIdentity(identity);
156        }
157    }
158
159    @Override
160    public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
161        synchronized (mLock) {
162            throwIfCalledByNotTrustedUidLocked();
163            throwIfShutdownLocked();
164            throwIfNotConnectedLocked();
165        }
166        final long identity = Binder.clearCallingIdentity();
167        try {
168            IBinder token = mAccessibilityManager.getWindowToken(windowId);
169            if (token == null) {
170                return false;
171            }
172            return mWindowManager.clearWindowContentFrameStats(token);
173        } finally {
174            Binder.restoreCallingIdentity(identity);
175        }
176    }
177
178    @Override
179    public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
180        synchronized (mLock) {
181            throwIfCalledByNotTrustedUidLocked();
182            throwIfShutdownLocked();
183            throwIfNotConnectedLocked();
184        }
185        final long identity = Binder.clearCallingIdentity();
186        try {
187            IBinder token = mAccessibilityManager.getWindowToken(windowId);
188            if (token == null) {
189                return null;
190            }
191            return mWindowManager.getWindowContentFrameStats(token);
192        } finally {
193            Binder.restoreCallingIdentity(identity);
194        }
195    }
196
197    @Override
198    public void clearWindowAnimationFrameStats() {
199        synchronized (mLock) {
200            throwIfCalledByNotTrustedUidLocked();
201            throwIfShutdownLocked();
202            throwIfNotConnectedLocked();
203        }
204        final long identity = Binder.clearCallingIdentity();
205        try {
206            SurfaceControl.clearAnimationFrameStats();
207        } finally {
208            Binder.restoreCallingIdentity(identity);
209        }
210    }
211
212    @Override
213    public WindowAnimationFrameStats getWindowAnimationFrameStats() {
214        synchronized (mLock) {
215            throwIfCalledByNotTrustedUidLocked();
216            throwIfShutdownLocked();
217            throwIfNotConnectedLocked();
218        }
219        final long identity = Binder.clearCallingIdentity();
220        try {
221            WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
222            SurfaceControl.getAnimationFrameStats(stats);
223            return stats;
224        } finally {
225            Binder.restoreCallingIdentity(identity);
226        }
227    }
228
229    @Override
230    public void executeShellCommand(String command, ParcelFileDescriptor sink)
231            throws RemoteException {
232        synchronized (mLock) {
233            throwIfCalledByNotTrustedUidLocked();
234            throwIfShutdownLocked();
235            throwIfNotConnectedLocked();
236        }
237
238        InputStream in = null;
239        OutputStream out = null;
240
241        try {
242            java.lang.Process process = Runtime.getRuntime().exec(command);
243
244            in = process.getInputStream();
245            out = new FileOutputStream(sink.getFileDescriptor());
246
247            final byte[] buffer = new byte[8192];
248            while (true) {
249                final int readByteCount = in.read(buffer);
250                if (readByteCount < 0) {
251                    break;
252                }
253                out.write(buffer, 0, readByteCount);
254            }
255        } catch (IOException ioe) {
256            throw new RuntimeException("Error running shell command", ioe);
257        } finally {
258            IoUtils.closeQuietly(in);
259            IoUtils.closeQuietly(out);
260            IoUtils.closeQuietly(sink);
261        }
262    }
263
264    @Override
265    public void shutdown() {
266        synchronized (mLock) {
267            if (isConnectedLocked()) {
268                throwIfCalledByNotTrustedUidLocked();
269            }
270            throwIfShutdownLocked();
271            mIsShutdown = true;
272            if (isConnectedLocked()) {
273                disconnect();
274            }
275        }
276    }
277
278    private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
279        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
280                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
281        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
282        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
283        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
284        info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
285                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
286        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
287                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
288                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
289                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
290        try {
291            // Calling out with a lock held is fine since if the system
292            // process is gone the client calling in will be killed.
293            manager.registerUiTestAutomationService(mToken, client, info);
294            mClient = client;
295        } catch (RemoteException re) {
296            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
297        }
298    }
299
300    private void unregisterUiTestAutomationServiceLocked() {
301        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
302              ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
303        try {
304            // Calling out with a lock held is fine since if the system
305            // process is gone the client calling in will be killed.
306            manager.unregisterUiTestAutomationService(mClient);
307            mClient = null;
308        } catch (RemoteException re) {
309            throw new IllegalStateException("Error while unregistering UiTestAutomationService",
310                    re);
311        }
312    }
313
314    private void storeRotationStateLocked() {
315        try {
316            if (mWindowManager.isRotationFrozen()) {
317                // Calling out with a lock held is fine since if the system
318                // process is gone the client calling in will be killed.
319                mInitialFrozenRotation = mWindowManager.getRotation();
320            }
321        } catch (RemoteException re) {
322            /* ignore */
323        }
324    }
325
326    private void restoreRotationStateLocked() {
327        try {
328            if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
329                // Calling out with a lock held is fine since if the system
330                // process is gone the client calling in will be killed.
331                mWindowManager.freezeRotation(mInitialFrozenRotation);
332            } else {
333                // Calling out with a lock held is fine since if the system
334                // process is gone the client calling in will be killed.
335                mWindowManager.thawRotation();
336            }
337        } catch (RemoteException re) {
338            /* ignore */
339        }
340    }
341
342    private boolean isConnectedLocked() {
343        return mClient != null;
344    }
345
346    private void throwIfShutdownLocked() {
347        if (mIsShutdown) {
348            throw new IllegalStateException("Connection shutdown!");
349        }
350    }
351
352    private void throwIfNotConnectedLocked() {
353        if (!isConnectedLocked()) {
354            throw new IllegalStateException("Not connected!");
355        }
356    }
357
358    private void throwIfCalledByNotTrustedUidLocked() {
359        final int callingUid = Binder.getCallingUid();
360        if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
361                && callingUid != 0 /*root*/) {
362            throw new SecurityException("Calling from not trusted UID!");
363        }
364    }
365}
366