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.Process;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.view.IWindowManager;
29import android.view.InputEvent;
30import android.view.SurfaceControl;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.IAccessibilityManager;
33
34/**
35 * This is a remote object that is passed from the shell to an instrumentation
36 * for enabling access to privileged operations which the shell can do and the
37 * instrumentation cannot. These privileged operations are needed for implementing
38 * a {@link UiAutomation} that enables across application testing by simulating
39 * user actions and performing screen introspection.
40 *
41 * @hide
42 */
43public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
44
45    private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
46
47    private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
48            ServiceManager.getService(Service.WINDOW_SERVICE));
49
50    private final Object mLock = new Object();
51
52    private final Binder mToken = new Binder();
53
54    private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
55
56    private IAccessibilityServiceClient mClient;
57
58    private boolean mIsShutdown;
59
60    private int mOwningUid;
61
62    public void connect(IAccessibilityServiceClient client) {
63        if (client == null) {
64            throw new IllegalArgumentException("Client cannot be null!");
65        }
66        synchronized (mLock) {
67            throwIfShutdownLocked();
68            if (isConnectedLocked()) {
69                throw new IllegalStateException("Already connected.");
70            }
71            mOwningUid = Binder.getCallingUid();
72            registerUiTestAutomationServiceLocked(client);
73            storeRotationStateLocked();
74        }
75    }
76
77    @Override
78    public void disconnect() {
79        synchronized (mLock) {
80            throwIfCalledByNotTrustedUidLocked();
81            throwIfShutdownLocked();
82            if (!isConnectedLocked()) {
83                throw new IllegalStateException("Already disconnected.");
84            }
85            mOwningUid = -1;
86            unregisterUiTestAutomationServiceLocked();
87            restoreRotationStateLocked();
88        }
89    }
90
91    @Override
92    public boolean injectInputEvent(InputEvent event, boolean sync) {
93        synchronized (mLock) {
94            throwIfCalledByNotTrustedUidLocked();
95            throwIfShutdownLocked();
96            throwIfNotConnectedLocked();
97        }
98        final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
99                : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
100        final long identity = Binder.clearCallingIdentity();
101        try {
102            return InputManager.getInstance().injectInputEvent(event, mode);
103        } finally {
104            Binder.restoreCallingIdentity(identity);
105        }
106    }
107
108    @Override
109    public boolean setRotation(int rotation) {
110        synchronized (mLock) {
111            throwIfCalledByNotTrustedUidLocked();
112            throwIfShutdownLocked();
113            throwIfNotConnectedLocked();
114        }
115        final long identity = Binder.clearCallingIdentity();
116        try {
117            if (rotation == UiAutomation.ROTATION_UNFREEZE) {
118                mWindowManager.thawRotation();
119            } else {
120                mWindowManager.freezeRotation(rotation);
121            }
122            return true;
123        } catch (RemoteException re) {
124            /* ignore */
125        } finally {
126            Binder.restoreCallingIdentity(identity);
127        }
128        return false;
129    }
130
131    @Override
132    public Bitmap takeScreenshot(int width, int height) {
133        synchronized (mLock) {
134            throwIfCalledByNotTrustedUidLocked();
135            throwIfShutdownLocked();
136            throwIfNotConnectedLocked();
137        }
138        final long identity = Binder.clearCallingIdentity();
139        try {
140            return SurfaceControl.screenshot(width, height);
141        } finally {
142            Binder.restoreCallingIdentity(identity);
143        }
144    }
145
146    @Override
147    public void shutdown() {
148        synchronized (mLock) {
149            if (isConnectedLocked()) {
150                throwIfCalledByNotTrustedUidLocked();
151            }
152            throwIfShutdownLocked();
153            mIsShutdown = true;
154            if (isConnectedLocked()) {
155                disconnect();
156            }
157        }
158    }
159
160    private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
161        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
162                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
163        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
164        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
165        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
166        info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
167                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
168        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
169                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
170                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
171                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
172        try {
173            // Calling out with a lock held is fine since if the system
174            // process is gone the client calling in will be killed.
175            manager.registerUiTestAutomationService(mToken, client, info);
176            mClient = client;
177        } catch (RemoteException re) {
178            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
179        }
180    }
181
182    private void unregisterUiTestAutomationServiceLocked() {
183        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
184              ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
185        try {
186            // Calling out with a lock held is fine since if the system
187            // process is gone the client calling in will be killed.
188            manager.unregisterUiTestAutomationService(mClient);
189            mClient = null;
190        } catch (RemoteException re) {
191            throw new IllegalStateException("Error while unregistering UiTestAutomationService",
192                    re);
193        }
194    }
195
196    private void storeRotationStateLocked() {
197        try {
198            if (mWindowManager.isRotationFrozen()) {
199                // Calling out with a lock held is fine since if the system
200                // process is gone the client calling in will be killed.
201                mInitialFrozenRotation = mWindowManager.getRotation();
202            }
203        } catch (RemoteException re) {
204            /* ignore */
205        }
206    }
207
208    private void restoreRotationStateLocked() {
209        try {
210            if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
211                // Calling out with a lock held is fine since if the system
212                // process is gone the client calling in will be killed.
213                mWindowManager.freezeRotation(mInitialFrozenRotation);
214            } else {
215                // Calling out with a lock held is fine since if the system
216                // process is gone the client calling in will be killed.
217                mWindowManager.thawRotation();
218            }
219        } catch (RemoteException re) {
220            /* ignore */
221        }
222    }
223
224    private boolean isConnectedLocked() {
225        return mClient != null;
226    }
227
228    private void throwIfShutdownLocked() {
229        if (mIsShutdown) {
230            throw new IllegalStateException("Connection shutdown!");
231        }
232    }
233
234    private void throwIfNotConnectedLocked() {
235        if (!isConnectedLocked()) {
236            throw new IllegalStateException("Not connected!");
237        }
238    }
239
240    private void throwIfCalledByNotTrustedUidLocked() {
241        final int callingUid = Binder.getCallingUid();
242        if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
243                && callingUid != 0 /*root*/) {
244            throw new SecurityException("Calling from not trusted UID!");
245        }
246    }
247}
248