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            throwIfCalledByNotTrustedUidLocked();
150            throwIfShutdownLocked();
151            mIsShutdown = true;
152            if (isConnectedLocked()) {
153                disconnect();
154            }
155        }
156    }
157
158    private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
159        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
160                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
161        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
162        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
163        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
164        info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
165                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
166        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
167                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
168                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
169                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
170        try {
171            // Calling out with a lock held is fine since if the system
172            // process is gone the client calling in will be killed.
173            manager.registerUiTestAutomationService(mToken, client, info);
174            mClient = client;
175        } catch (RemoteException re) {
176            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
177        }
178    }
179
180    private void unregisterUiTestAutomationServiceLocked() {
181        IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
182              ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
183        try {
184            // Calling out with a lock held is fine since if the system
185            // process is gone the client calling in will be killed.
186            manager.unregisterUiTestAutomationService(mClient);
187            mClient = null;
188        } catch (RemoteException re) {
189            throw new IllegalStateException("Error while unregistering UiTestAutomationService",
190                    re);
191        }
192    }
193
194    private void storeRotationStateLocked() {
195        try {
196            if (mWindowManager.isRotationFrozen()) {
197                // Calling out with a lock held is fine since if the system
198                // process is gone the client calling in will be killed.
199                mInitialFrozenRotation = mWindowManager.getRotation();
200            }
201        } catch (RemoteException re) {
202            /* ignore */
203        }
204    }
205
206    private void restoreRotationStateLocked() {
207        try {
208            if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
209                // Calling out with a lock held is fine since if the system
210                // process is gone the client calling in will be killed.
211                mWindowManager.freezeRotation(mInitialFrozenRotation);
212            } else {
213                // Calling out with a lock held is fine since if the system
214                // process is gone the client calling in will be killed.
215                mWindowManager.thawRotation();
216            }
217        } catch (RemoteException re) {
218            /* ignore */
219        }
220    }
221
222    private boolean isConnectedLocked() {
223        return mClient != null;
224    }
225
226    private void throwIfShutdownLocked() {
227        if (mIsShutdown) {
228            throw new IllegalStateException("Connection shutdown!");
229        }
230    }
231
232    private void throwIfNotConnectedLocked() {
233        if (!isConnectedLocked()) {
234            throw new IllegalStateException("Not connected!");
235        }
236    }
237
238    private void throwIfCalledByNotTrustedUidLocked() {
239        final int callingUid = Binder.getCallingUid();
240        if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
241                && callingUid != 0 /*root*/) {
242            throw new SecurityException("Calling from not trusted UID!");
243        }
244    }
245}
246