blob: 06ef4728814697e046b0a2a3074f86c2304ce640 [file] [log] [blame]
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001/*
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;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080030import android.view.SurfaceControl;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080031import 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 int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
53
54 private IAccessibilityServiceClient mClient;
55
56 private boolean mIsShutdown;
57
58 private int mOwningUid;
59
60 public void connect(IAccessibilityServiceClient client) {
61 if (client == null) {
62 throw new IllegalArgumentException("Client cannot be null!");
63 }
64 synchronized (mLock) {
65 throwIfShutdownLocked();
66 if (isConnectedLocked()) {
67 throw new IllegalStateException("Already connected.");
68 }
69 mOwningUid = Binder.getCallingUid();
70 registerUiTestAutomationServiceLocked(client);
71 storeRotationStateLocked();
72 }
73 }
74
75 @Override
76 public void disconnect() {
77 synchronized (mLock) {
78 throwIfCalledByNotTrustedUidLocked();
79 throwIfShutdownLocked();
80 if (!isConnectedLocked()) {
81 throw new IllegalStateException("Already disconnected.");
82 }
83 mOwningUid = -1;
84 unregisterUiTestAutomationServiceLocked();
85 restoreRotationStateLocked();
86 }
87 }
88
89 @Override
90 public boolean injectInputEvent(InputEvent event, boolean sync) {
91 synchronized (mLock) {
92 throwIfCalledByNotTrustedUidLocked();
93 throwIfShutdownLocked();
94 throwIfNotConnectedLocked();
95 }
96 final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
97 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
98 final long identity = Binder.clearCallingIdentity();
99 try {
100 return InputManager.getInstance().injectInputEvent(event, mode);
101 } finally {
102 Binder.restoreCallingIdentity(identity);
103 }
104 }
105
106 @Override
107 public boolean setRotation(int rotation) {
108 synchronized (mLock) {
109 throwIfCalledByNotTrustedUidLocked();
110 throwIfShutdownLocked();
111 throwIfNotConnectedLocked();
112 }
113 final long identity = Binder.clearCallingIdentity();
114 try {
115 if (rotation == UiAutomation.ROTATION_UNFREEZE) {
116 mWindowManager.thawRotation();
117 } else {
118 mWindowManager.freezeRotation(rotation);
119 }
120 return true;
121 } catch (RemoteException re) {
122 /* ignore */
123 } finally {
124 Binder.restoreCallingIdentity(identity);
125 }
126 return false;
127 }
128
129 @Override
130 public Bitmap takeScreenshot(int width, int height) {
131 synchronized (mLock) {
132 throwIfCalledByNotTrustedUidLocked();
133 throwIfShutdownLocked();
134 throwIfNotConnectedLocked();
135 }
136 final long identity = Binder.clearCallingIdentity();
137 try {
Mathias Agopian3866f0d2013-02-11 22:08:48 -0800138 return SurfaceControl.screenshot(width, height);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800139 } finally {
140 Binder.restoreCallingIdentity(identity);
141 }
142 }
143
144 @Override
145 public void shutdown() {
146 synchronized (mLock) {
147 throwIfCalledByNotTrustedUidLocked();
148 throwIfShutdownLocked();
149 mIsShutdown = true;
150 if (isConnectedLocked()) {
151 disconnect();
152 }
153 }
154 }
155
156 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
157 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
158 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
159 AccessibilityServiceInfo info = new AccessibilityServiceInfo();
160 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
161 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
162 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
163 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
164 try {
165 // Calling out with a lock held is fine since if the system
166 // process is gone the client calling in will be killed.
167 manager.registerUiTestAutomationService(client, info);
168 mClient = client;
169 } catch (RemoteException re) {
170 throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
171 }
172 }
173
174 private void unregisterUiTestAutomationServiceLocked() {
175 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
176 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
177 try {
178 // Calling out with a lock held is fine since if the system
179 // process is gone the client calling in will be killed.
180 manager.unregisterUiTestAutomationService(mClient);
181 mClient = null;
182 } catch (RemoteException re) {
183 throw new IllegalStateException("Error while unregistering UiTestAutomationService",
184 re);
185 }
186 }
187
188 private void storeRotationStateLocked() {
189 try {
190 if (mWindowManager.isRotationFrozen()) {
191 // Calling out with a lock held is fine since if the system
192 // process is gone the client calling in will be killed.
193 mInitialFrozenRotation = mWindowManager.getRotation();
194 }
195 } catch (RemoteException re) {
196 /* ignore */
197 }
198 }
199
200 private void restoreRotationStateLocked() {
201 try {
202 if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
203 // Calling out with a lock held is fine since if the system
204 // process is gone the client calling in will be killed.
205 mWindowManager.freezeRotation(mInitialFrozenRotation);
206 } else {
207 // Calling out with a lock held is fine since if the system
208 // process is gone the client calling in will be killed.
209 mWindowManager.thawRotation();
210 }
211 } catch (RemoteException re) {
212 /* ignore */
213 }
214 }
215
216 private boolean isConnectedLocked() {
217 return mClient != null;
218 }
219
220 private void throwIfShutdownLocked() {
221 if (mIsShutdown) {
222 throw new IllegalStateException("Connection shutdown!");
223 }
224 }
225
226 private void throwIfNotConnectedLocked() {
227 if (!isConnectedLocked()) {
228 throw new IllegalStateException("Not connected!");
229 }
230 }
231
232 private void throwIfCalledByNotTrustedUidLocked() {
233 final int callingUid = Binder.getCallingUid();
234 if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
235 && callingUid != 0 /*root*/) {
236 throw new SecurityException("Calling from not trusted UID!");
237 }
238 }
239}