blob: 91b0d7cd68baac932d4c727f7ba1f84959b01621 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
/**
* This is a remote object that is passed from the shell to an instrumentation
* for enabling access to privileged operations which the shell can do and the
* instrumentation cannot. These privileged operations are needed for implementing
* a {@link UiAutomation} that enables across application testing by simulating
* user actions and performing screen introspection.
*
* @hide
*/
public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Service.WINDOW_SERVICE));
private final Object mLock = new Object();
private final Binder mToken = new Binder();
private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
private IAccessibilityServiceClient mClient;
private boolean mIsShutdown;
private int mOwningUid;
public void connect(IAccessibilityServiceClient client) {
if (client == null) {
throw new IllegalArgumentException("Client cannot be null!");
}
synchronized (mLock) {
throwIfShutdownLocked();
if (isConnectedLocked()) {
throw new IllegalStateException("Already connected.");
}
mOwningUid = Binder.getCallingUid();
registerUiTestAutomationServiceLocked(client);
storeRotationStateLocked();
}
}
@Override
public void disconnect() {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
if (!isConnectedLocked()) {
throw new IllegalStateException("Already disconnected.");
}
mOwningUid = -1;
unregisterUiTestAutomationServiceLocked();
restoreRotationStateLocked();
}
}
@Override
public boolean injectInputEvent(InputEvent event, boolean sync) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
: InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
final long identity = Binder.clearCallingIdentity();
try {
return InputManager.getInstance().injectInputEvent(event, mode);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public boolean setRotation(int rotation) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
if (rotation == UiAutomation.ROTATION_UNFREEZE) {
mWindowManager.thawRotation();
} else {
mWindowManager.freezeRotation(rotation);
}
return true;
} catch (RemoteException re) {
/* ignore */
} finally {
Binder.restoreCallingIdentity(identity);
}
return false;
}
@Override
public Bitmap takeScreenshot(int width, int height) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
return SurfaceControl.screenshot(width, height);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void shutdown() {
synchronized (mLock) {
if (isConnectedLocked()) {
throwIfCalledByNotTrustedUidLocked();
}
throwIfShutdownLocked();
mIsShutdown = true;
if (isConnectedLocked()) {
disconnect();
}
}
}
private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
| AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
manager.registerUiTestAutomationService(mToken, client, info);
mClient = client;
} catch (RemoteException re) {
throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
}
}
private void unregisterUiTestAutomationServiceLocked() {
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
manager.unregisterUiTestAutomationService(mClient);
mClient = null;
} catch (RemoteException re) {
throw new IllegalStateException("Error while unregistering UiTestAutomationService",
re);
}
}
private void storeRotationStateLocked() {
try {
if (mWindowManager.isRotationFrozen()) {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
mInitialFrozenRotation = mWindowManager.getRotation();
}
} catch (RemoteException re) {
/* ignore */
}
}
private void restoreRotationStateLocked() {
try {
if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
mWindowManager.freezeRotation(mInitialFrozenRotation);
} else {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
mWindowManager.thawRotation();
}
} catch (RemoteException re) {
/* ignore */
}
}
private boolean isConnectedLocked() {
return mClient != null;
}
private void throwIfShutdownLocked() {
if (mIsShutdown) {
throw new IllegalStateException("Connection shutdown!");
}
}
private void throwIfNotConnectedLocked() {
if (!isConnectedLocked()) {
throw new IllegalStateException("Not connected!");
}
}
private void throwIfCalledByNotTrustedUidLocked() {
final int callingUid = Binder.getCallingUid();
if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
&& callingUid != 0 /*root*/) {
throw new SecurityException("Calling from not trusted UID!");
}
}
}