727 lines
26 KiB
Java
727 lines
26 KiB
Java
![]() |
/*
|
||
|
* 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 static android.view.Display.DEFAULT_DISPLAY;
|
||
|
|
||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||
|
import android.accessibilityservice.IAccessibilityServiceClient;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.UserIdInt;
|
||
|
import android.companion.virtual.VirtualDeviceManager;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.Context;
|
||
|
import android.graphics.Rect;
|
||
|
import android.hardware.input.InputManager;
|
||
|
import android.hardware.input.InputManagerGlobal;
|
||
|
import android.os.Binder;
|
||
|
import android.os.Build;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.ParcelFileDescriptor;
|
||
|
import android.os.Process;
|
||
|
import android.os.RemoteException;
|
||
|
import android.os.ServiceManager;
|
||
|
import android.os.UserHandle;
|
||
|
import android.permission.IPermissionManager;
|
||
|
import android.util.Log;
|
||
|
import android.view.IWindowManager;
|
||
|
import android.view.InputDevice;
|
||
|
import android.view.InputEvent;
|
||
|
import android.view.KeyEvent;
|
||
|
import android.view.MotionEvent;
|
||
|
import android.view.SurfaceControl;
|
||
|
import android.view.WindowAnimationFrameStats;
|
||
|
import android.view.WindowContentFrameStats;
|
||
|
import android.view.accessibility.AccessibilityEvent;
|
||
|
import android.view.accessibility.IAccessibilityManager;
|
||
|
import android.window.ScreenCapture;
|
||
|
import android.window.ScreenCapture.CaptureArgs;
|
||
|
|
||
|
import libcore.io.IoUtils;
|
||
|
|
||
|
import java.io.FileInputStream;
|
||
|
import java.io.FileOutputStream;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.OutputStream;
|
||
|
import java.util.List;
|
||
|
|
||
|
/**
|
||
|
* 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 String TAG = "UiAutomationConnection";
|
||
|
|
||
|
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 IPermissionManager mPermissionManager = IPermissionManager.Stub
|
||
|
.asInterface(ServiceManager.getService("permissionmgr"));
|
||
|
|
||
|
private final IActivityManager mActivityManager = IActivityManager.Stub
|
||
|
.asInterface(ServiceManager.getService("activity"));
|
||
|
|
||
|
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;
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public UiAutomationConnection() {
|
||
|
Log.d(TAG, "Created on user " + Process.myUserHandle());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void connect(IAccessibilityServiceClient client, int flags) {
|
||
|
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,
|
||
|
Binder.getCallingUserHandle().getIdentifier(), flags);
|
||
|
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, boolean waitForAnimations) {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
|
||
|
final boolean syncTransactionsBefore;
|
||
|
final boolean syncTransactionsAfter;
|
||
|
if (event instanceof KeyEvent) {
|
||
|
KeyEvent keyEvent = (KeyEvent) event;
|
||
|
syncTransactionsBefore = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
|
||
|
syncTransactionsAfter = keyEvent.getAction() == KeyEvent.ACTION_UP;
|
||
|
} else {
|
||
|
MotionEvent motionEvent = (MotionEvent) event;
|
||
|
syncTransactionsBefore = motionEvent.getAction() == MotionEvent.ACTION_DOWN
|
||
|
|| motionEvent.isFromSource(InputDevice.SOURCE_MOUSE);
|
||
|
syncTransactionsAfter = motionEvent.getAction() == MotionEvent.ACTION_UP;
|
||
|
}
|
||
|
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
if (syncTransactionsBefore) {
|
||
|
mWindowManager.syncInputTransactions(waitForAnimations);
|
||
|
}
|
||
|
|
||
|
final boolean result = InputManagerGlobal.getInstance().injectInputEvent(event,
|
||
|
sync ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
|
||
|
: InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||
|
|
||
|
if (syncTransactionsAfter) {
|
||
|
mWindowManager.syncInputTransactions(waitForAnimations);
|
||
|
}
|
||
|
return result;
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
mAccessibilityManager.injectInputEventToInputFilter(event);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void syncInputTransactions(boolean waitForAnimations) {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
mWindowManager.syncInputTransactions(waitForAnimations);
|
||
|
} catch (RemoteException e) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean setRotation(int rotation) {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
if (rotation == UiAutomation.ROTATION_UNFREEZE) {
|
||
|
mWindowManager.thawRotation(/* caller= */ "UiAutomationConnection#setRotation");
|
||
|
} else {
|
||
|
mWindowManager.freezeRotation(rotation,
|
||
|
/* caller= */ "UiAutomationConnection#setRotation");
|
||
|
}
|
||
|
return true;
|
||
|
} catch (RemoteException re) {
|
||
|
/* ignore */
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
|
||
|
.setSourceCrop(crop)
|
||
|
.build();
|
||
|
mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener);
|
||
|
} catch (RemoteException re) {
|
||
|
re.rethrowAsRuntimeException();
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
@Override
|
||
|
public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl,
|
||
|
ScreenCapture.ScreenCaptureListener listener) {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
ScreenCapture.LayerCaptureArgs args =
|
||
|
new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl)
|
||
|
.setChildrenOnly(false)
|
||
|
.build();
|
||
|
int status = ScreenCapture.captureLayers(args, listener);
|
||
|
|
||
|
if (status != 0) {
|
||
|
return false;
|
||
|
}
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
int callingUserId = UserHandle.getCallingUserId();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
|
||
|
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();
|
||
|
}
|
||
|
int callingUserId = UserHandle.getCallingUserId();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Grants permission for the {@link Context#DEVICE_ID_DEFAULT default device}
|
||
|
*/
|
||
|
@Override
|
||
|
public void grantRuntimePermission(String packageName, String permission, int userId)
|
||
|
throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mPermissionManager.grantRuntimePermission(packageName, permission,
|
||
|
VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Revokes permission for the {@link Context#DEVICE_ID_DEFAULT default device}
|
||
|
*/
|
||
|
@Override
|
||
|
public void revokeRuntimePermission(String packageName, String permission, int userId)
|
||
|
throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mPermissionManager.revokeRuntimePermission(packageName, permission,
|
||
|
VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId, null);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void adoptShellPermissionIdentity(int uid, @Nullable String[] permissions)
|
||
|
throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.startDelegateShellPermissionIdentity(uid, permissions);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void dropShellPermissionIdentity() throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.stopDelegateShellPermissionIdentity();
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Nullable
|
||
|
public List<String> getAdoptedShellPermissions() throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
return mActivityManager.getDelegatedShellPermissions();
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void addOverridePermissionState(int uid, String permission, int result)
|
||
|
throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final int callingUid = Binder.getCallingUid();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.addOverridePermissionState(callingUid, uid, permission, result);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void removeOverridePermissionState(int uid, String permission) throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final int callingUid = Binder.getCallingUid();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.removeOverridePermissionState(callingUid, uid, permission);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void clearOverridePermissionStates(int uid) throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final int callingUid = Binder.getCallingUid();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.clearOverridePermissionStates(callingUid, uid);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void clearAllOverridePermissionStates() throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final int callingUid = Binder.getCallingUid();
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mActivityManager.clearAllOverridePermissionStates(callingUid);
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class Repeater implements Runnable {
|
||
|
// Continuously read readFrom and write back to writeTo until EOF is encountered
|
||
|
private final InputStream readFrom;
|
||
|
private final OutputStream writeTo;
|
||
|
public Repeater (InputStream readFrom, OutputStream writeTo) {
|
||
|
this.readFrom = readFrom;
|
||
|
this.writeTo = writeTo;
|
||
|
}
|
||
|
@Override
|
||
|
public void run() {
|
||
|
try {
|
||
|
final byte[] buffer = new byte[8192];
|
||
|
int readByteCount;
|
||
|
while (true) {
|
||
|
readByteCount = readFrom.read(buffer);
|
||
|
if (readByteCount < 0) {
|
||
|
break;
|
||
|
}
|
||
|
writeTo.write(buffer, 0, readByteCount);
|
||
|
writeTo.flush();
|
||
|
}
|
||
|
} catch (IOException ignored) {
|
||
|
} finally {
|
||
|
IoUtils.closeQuietly(readFrom);
|
||
|
IoUtils.closeQuietly(writeTo);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void executeShellCommand(final String command, final ParcelFileDescriptor sink,
|
||
|
final ParcelFileDescriptor source) throws RemoteException {
|
||
|
executeShellCommandWithStderr(command, sink, source, null /* stderrSink */);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink,
|
||
|
final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink)
|
||
|
throws RemoteException {
|
||
|
synchronized (mLock) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
throwIfShutdownLocked();
|
||
|
throwIfNotConnectedLocked();
|
||
|
}
|
||
|
final java.lang.Process process;
|
||
|
|
||
|
try {
|
||
|
process = Runtime.getRuntime().exec(command);
|
||
|
} catch (IOException exc) {
|
||
|
throw new RuntimeException("Error running shell command '" + command + "'", exc);
|
||
|
}
|
||
|
|
||
|
// Read from process and write to pipe
|
||
|
final Thread readFromProcess;
|
||
|
if (sink != null) {
|
||
|
InputStream sink_in = process.getInputStream();;
|
||
|
OutputStream sink_out = new FileOutputStream(sink.getFileDescriptor());
|
||
|
|
||
|
readFromProcess = new Thread(new Repeater(sink_in, sink_out));
|
||
|
readFromProcess.start();
|
||
|
} else {
|
||
|
readFromProcess = null;
|
||
|
}
|
||
|
|
||
|
// Read from pipe and write to process
|
||
|
final Thread writeToProcess;
|
||
|
if (source != null) {
|
||
|
OutputStream source_out = process.getOutputStream();
|
||
|
InputStream source_in = new FileInputStream(source.getFileDescriptor());
|
||
|
|
||
|
writeToProcess = new Thread(new Repeater(source_in, source_out));
|
||
|
writeToProcess.start();
|
||
|
} else {
|
||
|
writeToProcess = null;
|
||
|
}
|
||
|
|
||
|
// Read from process stderr and write to pipe
|
||
|
final Thread readStderrFromProcess;
|
||
|
if (stderrSink != null) {
|
||
|
InputStream sink_in = process.getErrorStream();
|
||
|
OutputStream sink_out = new FileOutputStream(stderrSink.getFileDescriptor());
|
||
|
|
||
|
readStderrFromProcess = new Thread(new Repeater(sink_in, sink_out));
|
||
|
readStderrFromProcess.start();
|
||
|
} else {
|
||
|
readStderrFromProcess = null;
|
||
|
}
|
||
|
|
||
|
Thread cleanup = new Thread(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
try {
|
||
|
if (writeToProcess != null) {
|
||
|
writeToProcess.join();
|
||
|
}
|
||
|
if (readFromProcess != null) {
|
||
|
readFromProcess.join();
|
||
|
}
|
||
|
if (readStderrFromProcess != null) {
|
||
|
readStderrFromProcess.join();
|
||
|
}
|
||
|
} catch (InterruptedException exc) {
|
||
|
Log.e(TAG, "At least one of the threads was interrupted");
|
||
|
}
|
||
|
IoUtils.closeQuietly(sink);
|
||
|
IoUtils.closeQuietly(source);
|
||
|
IoUtils.closeQuietly(stderrSink);
|
||
|
process.destroy();
|
||
|
}
|
||
|
});
|
||
|
cleanup.start();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void shutdown() {
|
||
|
synchronized (mLock) {
|
||
|
if (isConnectedLocked()) {
|
||
|
throwIfCalledByNotTrustedUidLocked();
|
||
|
}
|
||
|
throwIfShutdownLocked();
|
||
|
mIsShutdown = true;
|
||
|
if (isConnectedLocked()) {
|
||
|
disconnect();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
|
||
|
@UserIdInt int userId, int flags) {
|
||
|
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
|
||
|
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
|
||
|
final 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
|
||
|
| AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
|
||
|
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
|
||
|
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
|
||
|
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
|
||
|
if ((flags & UiAutomation.FLAG_NOT_ACCESSIBILITY_TOOL) == 0) {
|
||
|
info.setAccessibilityTool(true);
|
||
|
}
|
||
|
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, userId, flags);
|
||
|
mClient = client;
|
||
|
} catch (RemoteException re) {
|
||
|
throw new IllegalStateException("Error while registering UiTestAutomationService for "
|
||
|
+ "user " + userId + ".", 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.getDefaultDisplayRotation();
|
||
|
}
|
||
|
} 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,
|
||
|
/* caller= */ "UiAutomationConnection#restoreRotationStateLocked");
|
||
|
} 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(
|
||
|
/* caller= */ "UiAutomationConnection#restoreRotationStateLocked");
|
||
|
}
|
||
|
} 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!");
|
||
|
}
|
||
|
}
|
||
|
}
|