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.content.pm.IPackageManager;
23import android.graphics.Bitmap;
24import android.hardware.input.InputManager;
25import android.os.Binder;
26import android.os.IBinder;
27import android.os.ParcelFileDescriptor;
28import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.UserHandle;
32import android.view.IWindowManager;
33import android.view.InputEvent;
34import android.view.SurfaceControl;
35import android.view.WindowAnimationFrameStats;
36import android.view.WindowContentFrameStats;
37import android.view.accessibility.AccessibilityEvent;
38import android.view.accessibility.IAccessibilityManager;
39
40import libcore.io.IoUtils;
41
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.io.InputStream;
45import java.io.OutputStream;
46
47/**
48 * This is a remote object that is passed from the shell to an instrumentation
49 * for enabling access to privileged operations which the shell can do and the
50 * instrumentation cannot. These privileged operations are needed for implementing
51 * a {@link UiAutomation} that enables across application testing by simulating
52 * user actions and performing screen introspection.
53 *
54 * @hide
55 */
56public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
57
58    private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
59
60    private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
61            ServiceManager.getService(Service.WINDOW_SERVICE));
62
63    private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
64            .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
65
66    private final IPackageManager mPackageManager = IPackageManager.Stub
67            .asInterface(ServiceManager.getService("package"));
68
69    private final Object mLock = new Object();
70
71    private final Binder mToken = new Binder();
72
73    private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
74
75    private IAccessibilityServiceClient mClient;
76
77    private boolean mIsShutdown;
78
79    private int mOwningUid;
80
81    @Override
82    public void connect(IAccessibilityServiceClient client, int flags) {
83        if (client == null) {
84            throw new IllegalArgumentException("Client cannot be null!");
85        }
86        synchronized (mLock) {
87            throwIfShutdownLocked();
88            if (isConnectedLocked()) {
89                throw new IllegalStateException("Already connected.");
90            }
91            mOwningUid = Binder.getCallingUid();
92            registerUiTestAutomationServiceLocked(client, flags);
93            storeRotationStateLocked();
94        }
95    }
96
97    @Override
98    public void disconnect() {
99        synchronized (mLock) {
100            throwIfCalledByNotTrustedUidLocked();
101            throwIfShutdownLocked();
102            if (!isConnectedLocked()) {
103                throw new IllegalStateException("Already disconnected.");
104            }
105            mOwningUid = -1;
106            unregisterUiTestAutomationServiceLocked();
107            restoreRotationStateLocked();
108        }
109    }
110
111    @Override
112    public boolean injectInputEvent(InputEvent event, boolean sync) {
113        synchronized (mLock) {
114            throwIfCalledByNotTrustedUidLocked();
115            throwIfShutdownLocked();
116            throwIfNotConnectedLocked();
117        }
118        final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
119                : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
120        final long identity = Binder.clearCallingIdentity();
121        try {
122            return InputManager.getInstance().injectInputEvent(event, mode);
123        } finally {
124            Binder.restoreCallingIdentity(identity);
125        }
126    }
127
128    @Override
129    public boolean setRotation(int rotation) {
130        synchronized (mLock) {
131            throwIfCalledByNotTrustedUidLocked();
132            throwIfShutdownLocked();
133            throwIfNotConnectedLocked();
134        }
135        final long identity = Binder.clearCallingIdentity();
136        try {
137            if (rotation == UiAutomation.ROTATION_UNFREEZE) {
138                mWindowManager.thawRotation();
139            } else {
140                mWindowManager.freezeRotation(rotation);
141            }
142            return true;
143        } catch (RemoteException re) {
144            /* ignore */
145        } finally {
146            Binder.restoreCallingIdentity(identity);
147        }
148        return false;
149    }
150
151    @Override
152    public Bitmap takeScreenshot(int width, int height) {
153        synchronized (mLock) {
154            throwIfCalledByNotTrustedUidLocked();
155            throwIfShutdownLocked();
156            throwIfNotConnectedLocked();
157        }
158        final long identity = Binder.clearCallingIdentity();
159        try {
160            return SurfaceControl.screenshot(width, height);
161        } finally {
162            Binder.restoreCallingIdentity(identity);
163        }
164    }
165
166    @Override
167    public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
168        synchronized (mLock) {
169            throwIfCalledByNotTrustedUidLocked();
170            throwIfShutdownLocked();
171            throwIfNotConnectedLocked();
172        }
173        int callingUserId = UserHandle.getCallingUserId();
174        final long identity = Binder.clearCallingIdentity();
175        try {
176            IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
177            if (token == null) {
178                return false;
179            }
180            return mWindowManager.clearWindowContentFrameStats(token);
181        } finally {
182            Binder.restoreCallingIdentity(identity);
183        }
184    }
185
186    @Override
187    public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
188        synchronized (mLock) {
189            throwIfCalledByNotTrustedUidLocked();
190            throwIfShutdownLocked();
191            throwIfNotConnectedLocked();
192        }
193        int callingUserId = UserHandle.getCallingUserId();
194        final long identity = Binder.clearCallingIdentity();
195        try {
196            IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
197            if (token == null) {
198                return null;
199            }
200            return mWindowManager.getWindowContentFrameStats(token);
201        } finally {
202            Binder.restoreCallingIdentity(identity);
203        }
204    }
205
206    @Override
207    public void clearWindowAnimationFrameStats() {
208        synchronized (mLock) {
209            throwIfCalledByNotTrustedUidLocked();
210            throwIfShutdownLocked();
211            throwIfNotConnectedLocked();
212        }
213        final long identity = Binder.clearCallingIdentity();
214        try {
215            SurfaceControl.clearAnimationFrameStats();
216        } finally {
217            Binder.restoreCallingIdentity(identity);
218        }
219    }
220
221    @Override
222    public WindowAnimationFrameStats getWindowAnimationFrameStats() {
223        synchronized (mLock) {
224            throwIfCalledByNotTrustedUidLocked();
225            throwIfShutdownLocked();
226            throwIfNotConnectedLocked();
227        }
228        final long identity = Binder.clearCallingIdentity();
229        try {
230            WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
231            SurfaceControl.getAnimationFrameStats(stats);
232            return stats;
233        } finally {
234            Binder.restoreCallingIdentity(identity);
235        }
236    }
237
238    @Override
239    public void grantRuntimePermission(String packageName, String permission, int userId)
240            throws RemoteException {
241        synchronized (mLock) {
242            throwIfCalledByNotTrustedUidLocked();
243            throwIfShutdownLocked();
244            throwIfNotConnectedLocked();
245        }
246        final long identity = Binder.clearCallingIdentity();
247        try {
248            mPackageManager.grantRuntimePermission(packageName, permission, userId);
249        } finally {
250            Binder.restoreCallingIdentity(identity);
251        }
252    }
253
254    @Override
255    public void revokeRuntimePermission(String packageName, String permission, int userId)
256            throws RemoteException {
257        synchronized (mLock) {
258            throwIfCalledByNotTrustedUidLocked();
259            throwIfShutdownLocked();
260            throwIfNotConnectedLocked();
261        }
262        final long identity = Binder.clearCallingIdentity();
263        try {
264            mPackageManager.revokeRuntimePermission(packageName, permission, userId);
265        } finally {
266            Binder.restoreCallingIdentity(identity);
267        }
268    }
269
270    @Override
271    public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
272            throws RemoteException {
273        synchronized (mLock) {
274            throwIfCalledByNotTrustedUidLocked();
275            throwIfShutdownLocked();
276            throwIfNotConnectedLocked();
277        }
278
279        Thread streamReader = new Thread() {
280            public void run() {
281                InputStream in = null;
282                OutputStream out = null;
283                java.lang.Process process = null;
284
285                try {
286                    process = Runtime.getRuntime().exec(command);
287
288                    in = process.getInputStream();
289                    out = new FileOutputStream(sink.getFileDescriptor());
290
291                    final byte[] buffer = new byte[8192];
292                    while (true) {
293                        final int readByteCount = in.read(buffer);
294                        if (readByteCount < 0) {
295                            break;
296                        }
297                        out.write(buffer, 0, readByteCount);
298                    }
299                } catch (IOException ioe) {
300                    throw new RuntimeException("Error running shell command", ioe);
301                } finally {
302                    if (process != null) {
303                        process.destroy();
304                    }
305                    IoUtils.closeQuietly(out);
306                    IoUtils.closeQuietly(sink);
307                }
308            };
309        };
310        streamReader.start();
311    }
312
313    @Override
314    public void shutdown() {
315        synchronized (mLock) {
316            if (isConnectedLocked()) {
317                throwIfCalledByNotTrustedUidLocked();
318            }
319            throwIfShutdownLocked();
320            mIsShutdown = true;
321            if (isConnectedLocked()) {
322                disconnect();
323            }
324        }
325    }
326
327    private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
328            int flags) {
329        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
330                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
331        final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
332        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
333        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
334        info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
335                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
336                | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
337        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
338                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
339                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
340                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
341        try {
342            // Calling out with a lock held is fine since if the system
343            // process is gone the client calling in will be killed.
344            manager.registerUiTestAutomationService(mToken, client, info, flags);
345            mClient = client;
346        } catch (RemoteException re) {
347            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
348        }
349    }
350
351    private void unregisterUiTestAutomationServiceLocked() {
352        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
353              ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
354        try {
355            // Calling out with a lock held is fine since if the system
356            // process is gone the client calling in will be killed.
357            manager.unregisterUiTestAutomationService(mClient);
358            mClient = null;
359        } catch (RemoteException re) {
360            throw new IllegalStateException("Error while unregistering UiTestAutomationService",
361                    re);
362        }
363    }
364
365    private void storeRotationStateLocked() {
366        try {
367            if (mWindowManager.isRotationFrozen()) {
368                // Calling out with a lock held is fine since if the system
369                // process is gone the client calling in will be killed.
370                mInitialFrozenRotation = mWindowManager.getRotation();
371            }
372        } catch (RemoteException re) {
373            /* ignore */
374        }
375    }
376
377    private void restoreRotationStateLocked() {
378        try {
379            if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
380                // Calling out with a lock held is fine since if the system
381                // process is gone the client calling in will be killed.
382                mWindowManager.freezeRotation(mInitialFrozenRotation);
383            } else {
384                // Calling out with a lock held is fine since if the system
385                // process is gone the client calling in will be killed.
386                mWindowManager.thawRotation();
387            }
388        } catch (RemoteException re) {
389            /* ignore */
390        }
391    }
392
393    private boolean isConnectedLocked() {
394        return mClient != null;
395    }
396
397    private void throwIfShutdownLocked() {
398        if (mIsShutdown) {
399            throw new IllegalStateException("Connection shutdown!");
400        }
401    }
402
403    private void throwIfNotConnectedLocked() {
404        if (!isConnectedLocked()) {
405            throw new IllegalStateException("Not connected!");
406        }
407    }
408
409    private void throwIfCalledByNotTrustedUidLocked() {
410        final int callingUid = Binder.getCallingUid();
411        if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
412                && callingUid != 0 /*root*/) {
413            throw new SecurityException("Calling from not trusted UID!");
414        }
415    }
416}
417