/* * 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.IBinder; import android.os.ParcelFileDescriptor; 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.WindowAnimationFrameStats; import android.view.WindowContentFrameStats; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; import libcore.io.IoUtils; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * 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 IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_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 boolean clearWindowContentFrameStats(int windowId) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } final long identity = Binder.clearCallingIdentity(); try { IBinder token = mAccessibilityManager.getWindowToken(windowId); if (token == null) { return false; } return mWindowManager.clearWindowContentFrameStats(token); } finally { Binder.restoreCallingIdentity(identity); } } @Override public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } final long identity = Binder.clearCallingIdentity(); try { IBinder token = mAccessibilityManager.getWindowToken(windowId); if (token == null) { return null; } return mWindowManager.getWindowContentFrameStats(token); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void clearWindowAnimationFrameStats() { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } final long identity = Binder.clearCallingIdentity(); try { SurfaceControl.clearAnimationFrameStats(); } finally { Binder.restoreCallingIdentity(identity); } } @Override public WindowAnimationFrameStats getWindowAnimationFrameStats() { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } final long identity = Binder.clearCallingIdentity(); try { WindowAnimationFrameStats stats = new WindowAnimationFrameStats(); SurfaceControl.getAnimationFrameStats(stats); return stats; } finally { Binder.restoreCallingIdentity(identity); } } @Override public void executeShellCommand(String command, ParcelFileDescriptor sink) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } InputStream in = null; OutputStream out = null; try { java.lang.Process process = Runtime.getRuntime().exec(command); in = process.getInputStream(); out = new FileOutputStream(sink.getFileDescriptor()); final byte[] buffer = new byte[8192]; while (true) { final int readByteCount = in.read(buffer); if (readByteCount < 0) { break; } out.write(buffer, 0, readByteCount); } } catch (IOException ioe) { throw new RuntimeException("Error running shell command", ioe); } finally { IoUtils.closeQuietly(in); IoUtils.closeQuietly(out); IoUtils.closeQuietly(sink); } } @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!"); } } }