| /* |
| * 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 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) { |
| 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; |
| 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(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!"); |
| } |
| } |
| } |