summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2013-01-02 10:25:37 -0800
committerSvetoslav <svetoslavganov@google.com>2013-01-22 17:56:53 -0800
commit80943d8daa6ab31ab5c486d57aea406aa0730d58 (patch)
treecb7738eff465941484aac3eb6ba15ba365aea576
parent64cae1a608c196c2bd1d9e7cfd2a1632fd0e5b83 (diff)
downloadframeworks_base-80943d8daa6ab31ab5c486d57aea406aa0730d58.zip
frameworks_base-80943d8daa6ab31ab5c486d57aea406aa0730d58.tar.gz
frameworks_base-80943d8daa6ab31ab5c486d57aea406aa0730d58.tar.bz2
Adding UI test automation APIs.
This change adds APIs support for implementing UI tests. Such tests do not rely on internal application structure and can span across application boundaries. UI automation APIs are encapsulated in the UiAutomation object that is provided by an Instrumentation object. It is initialized by the system and can be used for both introspecting the screen and performing interactions simulating a user. UI test are normal instrumentation tests and are executed on the device. UiAutomation uses the accessibility APIs to introspect the screen and a special delegate object to perform privileged operations such as injecting input events. Since instrumentation tests are invoked by a shell command, the shell program launching the tests creates a delegate object and passes it as an argument to started instrumentation. This delegate allows the APK that runs the tests to access some privileged operations protected by a signature level permissions which are explicitly granted to the shell user. The UiAutomation object also supports running tests in the legacy way where the tests are run as a Java shell program. This enables existing UiAutomator tests to keep working while the new ones should be implemented using the new APIs. The UiAutomation object exposes lower level APIs which allow simulation of arbitrary user interactions and writing complete UI test cases. Clients, such as UiAutomator, are encouraged to implement higher- level APIs which minimize development effort and can be used as a helper library by the test developer. The benefit of this change is decoupling UiAutomator from the system since the former was calling hidden APIs which required that it is bundled in the system image. This prevented UiAutomator from being evolved separately from the system. Also UiAutomator was creating additional API surface in the system image. Another benefit of the new design is that now test cases have access to a context and can use public platform APIs in addition to the UiAutomator ones. Further, third-parties can develop their own higher level test APIs on top of the lower level ones exposes by UiAutomation. bug:8028258 Also this change adds the fully qualified resource name of the view's id in the emitted AccessibilityNodeInfo if a special flag is set while configuring the accessibility service. Also added is API for looking up node infos by this id. The id resource name is relatively more stable compared to the generaed id number which may change from one build to another. This API facilitate reuing the already defined ids for UI automation. bug:7678973 Change-Id: I589ad14790320dec8a33095953926c2a2dd0228b
-rw-r--r--Android.mk3
-rw-r--r--api/current.txt26
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java6
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java9
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java10
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl8
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java496
-rw-r--r--core/java/android/app/ActivityManagerNative.java10
-rw-r--r--core/java/android/app/ActivityThread.java14
-rw-r--r--core/java/android/app/ApplicationThreadNative.java18
-rw-r--r--core/java/android/app/ContextImpl.java2
-rw-r--r--core/java/android/app/IActivityManager.java4
-rw-r--r--core/java/android/app/IApplicationThread.java3
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl40
-rw-r--r--core/java/android/app/Instrumentation.java47
-rw-r--r--core/java/android/app/UiAutomation.java597
-rw-r--r--core/java/android/app/UiAutomationConnection.java239
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java127
-rw-r--r--core/java/android/view/IWindowManager.aidl7
-rw-r--r--core/java/android/view/View.java32
-rw-r--r--core/java/android/view/ViewRootImpl.java11
-rw-r--r--core/java/android/view/WindowManagerPolicy.java10
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java26
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java75
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl6
-rw-r--r--core/java/android/widget/AbsListView.java2
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindowManager.java5
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java59
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java20
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java2
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--test-runner/src/android/test/AndroidTestRunner.java1
-rw-r--r--test-runner/src/android/test/InstrumentationTestRunner.java6
33 files changed, 1291 insertions, 635 deletions
diff --git a/Android.mk b/Android.mk
index f45f977..1fb6b52 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,8 +78,9 @@ LOCAL_SRC_FILES += \
core/java/android/app/IThumbnailReceiver.aidl \
core/java/android/app/IThumbnailRetriever.aidl \
core/java/android/app/ITransientNotification.aidl \
+ core/java/android/app/IUiAutomationConnection.aidl \
core/java/android/app/IUiModeManager.aidl \
- core/java/android/app/IUserSwitchObserver.aidl \
+ core/java/android/app/IUserSwitchObserver.aidl \
core/java/android/app/IWallpaperManager.aidl \
core/java/android/app/IWallpaperManagerCallback.aidl \
core/java/android/app/admin/IDevicePolicyManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index d367a00..43c5b0f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2103,6 +2103,7 @@ package android.accessibilityservice {
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 8; // 0x8
field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
field public int eventTypes;
field public int feedbackType;
@@ -3579,6 +3580,7 @@ package android.app {
method public android.content.ComponentName getComponentName();
method public android.content.Context getContext();
method public android.content.Context getTargetContext();
+ method public android.app.UiAutomation getUiAutomation();
method public boolean invokeContextMenuAction(android.app.Activity, int, int);
method public boolean invokeMenuActionSync(android.app.Activity, int, int);
method public boolean isProfiling();
@@ -4136,6 +4138,26 @@ package android.app {
method public abstract void onTimeSet(android.widget.TimePicker, int, int);
}
+ public final class UiAutomation {
+ method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, com.android.internal.util.Predicate<android.view.accessibility.AccessibilityEvent>, long) throws java.util.concurrent.TimeoutException;
+ method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
+ method public boolean injectInputEvent(android.view.InputEvent, boolean);
+ method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener);
+ method public boolean setRotation(int);
+ method public android.graphics.Bitmap takeScreenshot();
+ method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException;
+ field public static final int ROTATION_FREEZE_0 = 0; // 0x0
+ field public static final int ROTATION_FREEZE_180 = 2; // 0x2
+ field public static final int ROTATION_FREEZE_270 = 3; // 0x3
+ field public static final int ROTATION_FREEZE_90 = 1; // 0x1
+ field public static final int ROTATION_FREEZE_CURRENT = -1; // 0xffffffff
+ field public static final int ROTATION_UNFREEZE = -2; // 0xfffffffe
+ }
+
+ public static abstract interface UiAutomation.OnAccessibilityEventListener {
+ method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ }
+
public class UiModeManager {
method public void disableCarMode(int);
method public void enableCarMode(int);
@@ -21294,6 +21316,7 @@ package android.test {
ctor public InstrumentationTestRunner();
method public junit.framework.TestSuite getAllTests();
method protected android.test.AndroidTestRunner getAndroidTestRunner();
+ method protected android.os.Bundle getArguments();
method public java.lang.ClassLoader getLoader();
method public junit.framework.TestSuite getTestSuite();
field public static final java.lang.String REPORT_KEY_NAME_CLASS = "class";
@@ -26240,6 +26263,7 @@ package android.view.accessibility {
method public void addChild(android.view.View, int);
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String);
+ method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(java.lang.String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
method public int getActions();
@@ -26255,6 +26279,7 @@ package android.view.accessibility {
method public java.lang.CharSequence getPackageName();
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method public java.lang.CharSequence getText();
+ method public java.lang.CharSequence getViewId();
method public int getWindowId();
method public boolean isAccessibilityFocused();
method public boolean isCheckable();
@@ -26302,6 +26327,7 @@ package android.view.accessibility {
method public void setSource(android.view.View);
method public void setSource(android.view.View, int);
method public void setText(java.lang.CharSequence);
+ method public void setViewId(java.lang.CharSequence);
method public void setVisibleToUser(boolean);
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index add7a23..b5574cf 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -24,6 +24,7 @@ import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.IInstrumentationWatcher;
import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -661,10 +662,13 @@ public class Am {
if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
InstrumentationWatcher watcher = null;
+ UiAutomationConnection connection = null;
if (wait) {
watcher = new InstrumentationWatcher();
watcher.setRawOutput(rawMode);
+ connection = new UiAutomationConnection();
}
+
float[] oldAnims = null;
if (no_window_animation) {
oldAnims = wm.getAnimationScales();
@@ -672,7 +676,7 @@ public class Am {
wm.setAnimationScale(1, 0.0f);
}
- if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, userId)) {
+ if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 7efe189..c5f51dc 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -339,7 +339,10 @@ public abstract class AccessibilityService extends Service {
private static final String LOG_TAG = "AccessibilityService";
- interface Callbacks {
+ /**
+ * @hide
+ */
+ public interface Callbacks {
public void onAccessibilityEvent(AccessibilityEvent event);
public void onInterrupt();
public void onServiceConnected();
@@ -538,8 +541,10 @@ public abstract class AccessibilityService extends Service {
/**
* Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
*/
- static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
+ public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 75a4f83..2006bc7 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -33,6 +33,7 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -152,6 +153,15 @@ public class AccessibilityServiceInfo implements Parcelable {
public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
/**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000008;
+
+ /**
* The event types an {@link AccessibilityService} is interested in.
* <p>
* <strong>Can be dynamically set at runtime.</strong>
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index f33f503..7a29f35 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -85,15 +85,15 @@ interface IAccessibilityServiceConnection {
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param id The id of the node.
+ * @param viewId The fully qualified resource name of the view id to find.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
* @return Whether the call succeeded.
*/
- boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long threadId);
+ boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
* Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
deleted file mode 100644
index 6837386..0000000
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2012 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.accessibilityservice;
-
-import android.accessibilityservice.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
-
-import com.android.internal.util.Predicate;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class represents a bridge that can be used for UI test
- * automation. It is responsible for connecting to the system,
- * keeping track of the last accessibility event, and exposing
- * window content querying APIs. This class is designed to be
- * used from both an Android application and a Java program
- * run from the shell.
- *
- * @hide
- */
-public class UiTestAutomationBridge {
-
- private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
-
- private static final int TIMEOUT_REGISTER_SERVICE = 5000;
-
- public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
-
- public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
-
- public static final int UNDEFINED = -1;
-
- private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS =
- AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
- | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
- | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
- private final Object mLock = new Object();
-
- private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
-
- private IAccessibilityServiceClientWrapper mListener;
-
- private AccessibilityEvent mLastEvent;
-
- private volatile boolean mWaitingForEventDelivery;
-
- private volatile boolean mUnprocessedEventAvailable;
-
- private HandlerThread mHandlerThread;
-
- /**
- * Gets the last received {@link AccessibilityEvent}.
- *
- * @return The event.
- */
- public AccessibilityEvent getLastAccessibilityEvent() {
- return mLastEvent;
- }
-
- /**
- * Callback for receiving an {@link AccessibilityEvent}.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- *
- * @param event The received event.
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- /* hook - do nothing */
- }
-
- /**
- * Callback for requests to stop feedback.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- */
- public void onInterrupt() {
- /* hook - do nothing */
- }
-
- /**
- * Connects this service.
- *
- * @throws IllegalStateException If already connected.
- */
- public void connect() {
- if (isConnected()) {
- throw new IllegalStateException("Already connected.");
- }
-
- // Serialize binder calls to a handler on a dedicated thread
- // different from the main since we expose APIs that block
- // the main thread waiting for a result the deliver of which
- // on the main thread will prevent that thread from waking up.
- // The serialization is needed also to ensure that events are
- // examined in delivery order. Otherwise, a fair locking
- // is needed for making sure the binder calls are interleaved
- // with check for the expected event and also to make sure the
- // binder threads are allowed to proceed in the received order.
- mHandlerThread = new HandlerThread("UiTestAutomationBridge");
- mHandlerThread.setDaemon(true);
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
-
- mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
- @Override
- public void onServiceConnected() {
- /* do nothing */
- }
-
- @Override
- public void onInterrupt() {
- UiTestAutomationBridge.this.onInterrupt();
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- synchronized (mLock) {
- while (true) {
- mLastEvent = AccessibilityEvent.obtain(event);
- if (!mWaitingForEventDelivery) {
- mLock.notifyAll();
- break;
- }
- if (!mUnprocessedEventAvailable) {
- mUnprocessedEventAvailable = true;
- mLock.notifyAll();
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- UiTestAutomationBridge.this.onAccessibilityEvent(event);
- }
-
- @Override
- public void onSetConnectionId(int connectionId) {
- synchronized (mLock) {
- mConnectionId = connectionId;
- mLock.notifyAll();
- }
- }
-
- @Override
- public boolean onGesture(int gestureId) {
- return false;
- }
- });
-
- final 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;
-
- try {
- manager.registerUiTestAutomationService(mListener, info);
- } catch (RemoteException re) {
- throw new IllegalStateException("Cound not register UiAutomationService.", re);
- }
-
- synchronized (mLock) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- if (isConnected()) {
- return;
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new IllegalStateException("Cound not register UiAutomationService.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Disconnects this service.
- *
- * @throws IllegalStateException If already disconnected.
- */
- public void disconnect() {
- if (!isConnected()) {
- throw new IllegalStateException("Already disconnected.");
- }
-
- mHandlerThread.quit();
-
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- try {
- manager.unregisterUiTestAutomationService(mListener);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
- }
- }
-
- /**
- * Gets whether this service is connected.
- *
- * @return True if connected.
- */
- public boolean isConnected() {
- return (mConnectionId != AccessibilityInteractionClient.NO_ID);
- }
-
- /**
- * Executes a command and waits for a specific accessibility event type up
- * to a given timeout.
- *
- * @param command The command to execute before starting to wait for the event.
- * @param predicate Predicate for recognizing the awaited event.
- * @param timeoutMillis The max wait time in milliseconds.
- */
- public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
- Predicate<AccessibilityEvent> predicate, long timeoutMillis)
- throws TimeoutException, Exception {
- // TODO: This is broken - remove from here when finalizing this as public APIs.
- synchronized (mLock) {
- // Prepare to wait for an event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- if (mLastEvent != null) {
- mLastEvent.recycle();
- mLastEvent = null;
- }
- // Execute the command.
- command.run();
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- // If the expected event is received, that's it.
- if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- return mLastEvent;
- }
- // Ask for another event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- throw new TimeoutException("Expacted event not received within: "
- + timeoutMillis + " ms.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Waits for the accessibility event stream to become idle, which is not to
- * have received a new accessibility event within <code>idleTimeout</code>,
- * and do so within a maximal global timeout as specified by
- * <code>globalTimeout</code>.
- *
- * @param idleTimeout The timeout between two event to consider the device idle.
- * @param globalTimeout The maximal global timeout in which to wait for idle.
- */
- public void waitForIdle(long idleTimeout, long globalTimeout) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- long lastEventTime = (mLastEvent != null)
- ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
- synchronized (mLock) {
- while (true) {
- final long currentTimeMillis = SystemClock.uptimeMillis();
- final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
- if (sinceLastEventTimeMillis > idleTimeout) {
- return;
- }
- if (mLastEvent != null) {
- lastEventTime = mLastEvent.getEventTime();
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- return;
- }
- try {
- mLock.wait(idleTimeout);
- } catch (InterruptedException e) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
- * window. The search is performed from the root node.
- *
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
- long accessibilityNodeId) {
- return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id.
- *
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
- * the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
- int accessibilityWindowId, long accessibilityNodeId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- accessibilityWindowId, accessibilityNodeId,
- FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id in the active
- * window. The search is performed from the root node.
- *
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
- return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
- * the window whose id is specified and starts from the node whose accessibility
- * id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
- accessibilityNodeId, viewId);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text in the active
- * window. The search is performed from the root node.
- *
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
- return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the window whose
- * id is specified and starts from the node whose accessibility id is
- * specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
- long accessibilityNodeId, String text) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
- accessibilityNodeId, text);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}
- * in the active window.
- *
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action,
- Bundle arguments) {
- return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
- int action, Bundle arguments) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
- accessibilityWindowId, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Gets the root {@link AccessibilityNodeInfo} in the active window.
- *
- * @return The root info.
- */
- public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
- ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
- }
-
- private void ensureValidConnection(int connectionId) {
- if (connectionId == UNDEFINED) {
- throw new IllegalStateException("UiAutomationService not connected."
- + " Did you call #register()?");
- }
- }
-}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 61b2067..ee126f4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
@@ -836,8 +835,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle arguments = data.readBundle();
IBinder b = data.readStrongBinder();
IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
+ b = data.readStrongBinder();
+ IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -2856,8 +2857,8 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException {
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2866,6 +2867,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
data.writeBundle(arguments);
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+ data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1271645..5478bb7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,7 +43,6 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
@@ -102,7 +101,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
-import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -420,6 +418,7 @@ public final class ActivityThread {
ComponentName instrumentationName;
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
+ IUiAutomationConnection instrumentationUiAutomationConnection;
int debugMode;
boolean enableOpenGlTrace;
boolean restrictedBackupMode;
@@ -724,9 +723,10 @@ public final class ActivityThread {
ComponentName instrumentationName, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) {
+ IUiAutomationConnection instrumentationUiConnection, int debugMode,
+ boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -742,6 +742,7 @@ public final class ActivityThread {
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
@@ -4337,7 +4338,8 @@ public final class ActivityThread {
}
mInstrumentation.init(this, instrContext, appContext,
- new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher);
+ new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
+ data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 63aa5f9..386e8d7 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -267,6 +267,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle testArgs = data.readBundle();
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
+ binder = data.readStrongBinder();
+ IUiAutomationConnection uiAutomationConnection =
+ IUiAutomationConnection.Stub.asInterface(binder);
int testMode = data.readInt();
boolean openGlTrace = data.readInt() != 0;
boolean restrictedBackupMode = (data.readInt() != 0);
@@ -277,8 +280,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName, profileFd, autoStopProfiler,
- testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode,
- persistent, config, compatInfo, services, coreSettings);
+ testArgs, testWatcher, uiAutomationConnection, testMode,
+ openGlTrace, restrictedBackupMode, persistent, config, compatInfo,
+ services, coreSettings);
return true;
}
@@ -863,10 +867,11 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName, String profileName,
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs,
- IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace,
- boolean restrictedBackupMode, boolean persistent,
- Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
+ IInstrumentationWatcher testWatcher,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -888,6 +893,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(autoStopProfiler ? 1 : 0);
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
+ data.writeStrongInterface(uiAutomationConnection);
data.writeInt(debugMode);
data.writeInt(openGlTrace ? 1 : 0);
data.writeInt(restrictedBackupMode ? 1 : 0);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fd4389e..e03d3fd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1475,7 +1475,7 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId());
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8af17a4..9b4fe61 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -158,8 +158,8 @@ public interface IActivityManager extends IInterface {
public void killApplicationProcess(String processName, int uid) throws RemoteException;
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException;
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 03a26d4..5dbbab4 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -90,7 +90,8 @@ public interface IApplicationThread extends IInterface {
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher,
- int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
new file mode 100644
index 0000000..09bf829
--- /dev/null
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.view.InputEvent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This interface contains privileged operations a shell program can perform
+ * on behalf of an instrumentation that it runs. These operations require
+ * special permissions which the shell user has but the instrumentation does
+ * not. Running privileged operations by the shell user on behalf of an
+ * instrumentation is needed for running UiTestCases.
+ *
+ * {@hide}
+ */
+interface IUiAutomationConnection {
+ void connect(IAccessibilityServiceClient client);
+ void disconnect();
+ boolean injectInputEvent(in InputEvent event, boolean sync);
+ boolean setRotation(int rotation);
+ Bitmap takeScreenshot(int width, int height);
+ void shutdown();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 39186c6..a2eeddd 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -49,7 +49,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
-
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
@@ -59,6 +58,7 @@ import java.util.List;
* &lt;instrumentation&gt; tag.
*/
public class Instrumentation {
+
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies the class that is writing the report. This can be used to provide more structured
@@ -73,7 +73,7 @@ public class Instrumentation {
* instrumentation can also be launched, and results collected, by an automated system.
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
-
+
private static final String TAG = "Instrumentation";
private final Object mSync = new Object();
@@ -86,9 +86,11 @@ public class Instrumentation {
private List<ActivityWaiter> mWaitingActivities;
private List<ActivityMonitor> mActivityMonitors;
private IInstrumentationWatcher mWatcher;
+ private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
+ private UiAutomation mUiAutomation;
public Instrumentation() {
}
@@ -1598,13 +1600,14 @@ public class Instrumentation {
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
- IInstrumentationWatcher watcher) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
+ mUiAutomationConnection = uiAutomationConnection;
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
@@ -1644,12 +1647,42 @@ public class Instrumentation {
}
}
+ /**
+ * Gets the {@link UiAutomation} instance.
+ * <p>
+ * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
+ * work across application boundaries while the APIs exposed by the instrumentation
+ * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
+ * not allow you to inject the event in an app different from the instrumentation
+ * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
+ * will work regardless of the current application.
+ * </p>
+ * <p>
+ * A typical test case should be using either the {@link UiAutomation} or
+ * {@link Instrumentation} APIs. Using both APIs at the same time is not
+ * a mistake by itself but a client has to be aware of the APIs limitations.
+ * </p>
+ * @return The UI automation instance.
+ *
+ * @see UiAutomation
+ */
+ public UiAutomation getUiAutomation() {
+ if (mUiAutomationConnection != null) {
+ if (mUiAutomation == null) {
+ mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
+ mUiAutomationConnection);
+ mUiAutomation.connect();
+ }
+ return mUiAutomation;
+ }
+ return null;
+ }
+
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
}
public void run() {
- IActivityManager am = ActivityManagerNative.getDefault();
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
} catch (RuntimeException e) {
@@ -1660,9 +1693,13 @@ public class Instrumentation {
startPerformanceSnapshot();
}
onStart();
+ if (mUiAutomation != null) {
+ mUiAutomation.disconnect();
+ mUiAutomation = null;
+ }
}
}
-
+
private static final class EmptyRunnable implements Runnable {
public void run() {
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
new file mode 100644
index 0000000..e611f6d
--- /dev/null
+++ b/core/java/android/app/UiAutomation.java
@@ -0,0 +1,597 @@
+/*
+ * 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.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputEvent;
+import android.view.Surface;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.internal.util.Predicate;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class for interacting with the device's UI by simulation user actions and
+ * introspection of the screen content. It relies on the platform accessibility
+ * APIs to introspect the screen and to perform some actions on the remote view
+ * tree. It also allows injecting of arbitrary raw input events simulating user
+ * interaction with keyboards and touch devices.
+ * <p>
+ * The APIs exposed by this class are low-level to maximize flexibility when
+ * developing UI test automation tools and libraries. Generally, a UiAutomation
+ * client should be using a higher-level library or implement high-level functions.
+ * For example, performing a tap on the screen requires construction and injecting
+ * of a touch down and up events which have to be delivered to the system by a
+ * call to {@link #injectInputEvent(InputEvent, boolean)}.
+ * </p>
+ * <p>
+ * The APIs exposed by this class operate across applications enabling a client
+ * to write tests that cover use cases spanning over multiple applications. For
+ * example, going to the settings application to change a setting and then
+ * interacting with another application whose behavior depends on that setting.
+ * </p>
+ */
+public final class UiAutomation {
+
+ private static final String LOG_TAG = UiAutomation.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int CONNECTION_ID_UNDEFINED = -1;
+
+ private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+
+ /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
+ public static final int ROTATION_UNFREEZE = -2;
+
+ /** Rotation constant: Freeze rotation to its current state. */
+ public static final int ROTATION_FREEZE_CURRENT = -1;
+
+ /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
+ public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
+
+ /** Rotation constant: Freeze rotation to 90 degrees . */
+ public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
+
+ /** Rotation constant: Freeze rotation to 180 degrees . */
+ public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
+
+ /** Rotation constant: Freeze rotation to 270 degrees . */
+ public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
+
+ private final Object mLock = new Object();
+
+ private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
+
+ private final IAccessibilityServiceClient mClient;
+
+ private final IUiAutomationConnection mUiAutomationConnection;
+
+ private int mConnectionId = CONNECTION_ID_UNDEFINED;
+
+ private OnAccessibilityEventListener mOnAccessibilityEventListener;
+
+ private boolean mWaitingForEventDelivery;
+
+ private long mLastEventTimeMillis;
+
+ private boolean mIsConnecting;
+
+ /**
+ * Listener for observing the {@link AccessibilityEvent} stream.
+ */
+ public static interface OnAccessibilityEventListener {
+
+ /**
+ * Callback for receiving an {@link AccessibilityEvent}.
+ * <p>
+ * <strong>Note:</strong> This method is <strong>NOT</strong> executed
+ * on the main test thread. The client is responsible for proper
+ * synchronization.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> It is responsibility of the client
+ * to recycle the received events to minimize object creation.
+ * </p>
+ *
+ * @param event The received event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided looper and perform requests for privileged
+ * operations on the provided connection.
+ *
+ * @param looper The looper on which to execute accessibility callbacks.
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Looper looper, IUiAutomationConnection connection) {
+ if (looper == null) {
+ throw new IllegalArgumentException("Looper cannot be null!");
+ }
+ if (connection == null) {
+ throw new IllegalArgumentException("Connection cannot be null!");
+ }
+ mUiAutomationConnection = connection;
+ mClient = new IAccessibilityServiceClientImpl(looper);
+ }
+
+ /**
+ * Connects this UiAutomation to the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void connect() {
+ synchronized (mLock) {
+ throwIfConnectedLocked();
+ if (mIsConnecting) {
+ return;
+ }
+ mIsConnecting = true;
+ }
+
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.connect(mClient);
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while connecting UiAutomation", re);
+ }
+
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ try {
+ while (true) {
+ if (isConnectedLocked()) {
+ break;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new RuntimeException("Error while connecting UiAutomation");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mIsConnecting = false;
+ }
+ }
+ }
+
+ /**
+ * Disconnects this UiAutomation from the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mIsConnecting) {
+ throw new IllegalStateException(
+ "Cannot call disconnect() while connecting!");
+ }
+ throwIfNotConnectedLocked();
+ mConnectionId = CONNECTION_ID_UNDEFINED;
+ }
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.disconnect();
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while disconnecting UiAutomation", re);
+ }
+ }
+
+ /**
+ * The id of the {@link IAccessibilityInteractionConnection} for querying
+ * the screen content. This is here for legacy purposes since some tools use
+ * hidden APIs to introspect the screen.
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ return mConnectionId;
+ }
+ }
+
+ /**
+ * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
+ *
+ * @param listener The callback.
+ */
+ public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
+ synchronized (mLock) {
+ mOnAccessibilityEventListener = listener;
+ }
+ }
+
+ /**
+ * Gets the root {@link AccessibilityNodeInfo} in the active window.
+ *
+ * @return The root info.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ final int connectionId;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
+ }
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ }
+
+ /**
+ * A method for injecting an arbitrary input event.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+ * </p>
+ * @param event The event to inject.
+ * @param sync Whether to inject the event synchronously.
+ * @return Whether event injection succeeded.
+ */
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.injectInputEvent(event, sync);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while injecting input event!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Sets the device rotation. A client can freeze the rotation in
+ * desired state or freeze the rotation to its current state or
+ * unfreeze the rotation (rotating the device changes its rotation
+ * state).
+ *
+ * @param rotation The desired rotation.
+ * @return Whether the rotation was set successfully.
+ *
+ * @see #ROTATION_FREEZE_0
+ * @see #ROTATION_FREEZE_90
+ * @see #ROTATION_FREEZE_180
+ * @see #ROTATION_FREEZE_270
+ * @see #ROTATION_FREEZE_CURRENT
+ * @see #ROTATION_UNFREEZE
+ */
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ switch (rotation) {
+ case ROTATION_FREEZE_0:
+ case ROTATION_FREEZE_90:
+ case ROTATION_FREEZE_180:
+ case ROTATION_FREEZE_270:
+ case ROTATION_UNFREEZE:
+ case ROTATION_FREEZE_CURRENT: {
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.setRotation(rotation);
+ return true;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while setting rotation!", re);
+ }
+ } return false;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation.");
+ }
+ }
+ }
+
+ /**
+ * Executes a command and waits for a specific accessibility event up to a
+ * given wait timeout. To detect a sequence of events one can implement a
+ * filter that keeps track of seen events of the expected sequence and
+ * returns true after the last event of that sequence is received.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
+ * </p>
+ * @param command The command to execute.
+ * @param filter Filter that recognizes the expected event.
+ * @param timeoutMillis The wait timeout in milliseconds.
+ *
+ * @throws TimeoutException If the expected event is not received within the timeout.
+ */
+ public AccessibilityEvent executeAndWaitForEvent(Runnable command,
+ Predicate<AccessibilityEvent> filter, long timeoutMillis) throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ mEventQueue.clear();
+ // Prepare to wait for an event.
+ mWaitingForEventDelivery = true;
+
+ // We will ignore events from previous interactions.
+ final long executionStartTimeMillis = SystemClock.uptimeMillis();
+
+ // Execute the command.
+ command.run();
+ try {
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ // Drain the event queue
+ while (!mEventQueue.isEmpty()) {
+ AccessibilityEvent event = mEventQueue.remove(0);
+ // Ignore events from previous interactions.
+ if (event.getEventTime() <= executionStartTimeMillis) {
+ continue;
+ }
+ if (filter.apply(event)) {
+ return event;
+ }
+ event.recycle();
+ }
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException("Expected event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mWaitingForEventDelivery = false;
+ mEventQueue.clear();
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Waits for the accessibility event stream to become idle, which is not to
+ * have received an accessibility event within <code>idleTimeoutMillis</code>.
+ * The total time spent to wait for an idle accessibility event stream is bounded
+ * by the <code>globalTimeoutMillis</code>.
+ *
+ * @param idleTimeoutMillis The timeout in milliseconds between two events
+ * to consider the device idle.
+ * @param globalTimeoutMillis The maximal global timeout in milliseconds in
+ * which to wait for an idle state.
+ *
+ * @throws TimeoutException If no idle state was detected within
+ * <code>globalTimeoutMillis.</code>
+ */
+ public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
+ throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ if (mLastEventTimeMillis <= 0) {
+ mLastEventTimeMillis = startTimeMillis;
+ }
+
+ while (true) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+ // Did we get idle state within the global timeout?
+ final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
+ final long remainingGlobalTimeMillis =
+ globalTimeoutMillis - elapsedGlobalTimeMillis;
+ if (remainingGlobalTimeMillis <= 0) {
+ throw new TimeoutException("No idle state with idle timeout: "
+ + idleTimeoutMillis + " within global timeout: "
+ + globalTimeoutMillis);
+ }
+ // Did we get an idle state within the idle timeout?
+ final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
+ final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
+ if (remainingIdleTimeMillis <= 0) {
+ return;
+ }
+ try {
+ mLock.wait(remainingIdleTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Takes a screenshot.
+ *
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(Display.DEFAULT_DISPLAY);
+ Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ final int displayWidth = displaySize.x;
+ final int displayHeight = displaySize.y;
+
+ final float screenshotWidth;
+ final float screenshotHeight;
+
+ final int rotation = display.getRotation();
+ switch (rotation) {
+ case ROTATION_FREEZE_0: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_90: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ case ROTATION_FREEZE_180: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_270: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: "
+ + rotation);
+ }
+ }
+
+ // Take the screenshot
+ Bitmap screenShot = null;
+ try {
+ // Calling out without a lock held.
+ screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
+ (int) screenshotHeight);
+ if (screenShot == null) {
+ return null;
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while taking screnshot!", re);
+ return null;
+ }
+
+ // Rotate the screenshot to the current orientation
+ if (rotation != ROTATION_FREEZE_0) {
+ Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(unrotatedScreenShot);
+ canvas.translate(unrotatedScreenShot.getWidth() / 2,
+ unrotatedScreenShot.getHeight() / 2);
+ canvas.rotate(getDegreesForRotation(rotation));
+ canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
+ canvas.drawBitmap(screenShot, 0, 0, null);
+ canvas.setBitmap(null);
+ screenShot = unrotatedScreenShot;
+ }
+
+ // Optimization
+ screenShot.setHasAlpha(false);
+
+ return screenShot;
+ }
+
+ private static float getDegreesForRotation(int value) {
+ switch (value) {
+ case Surface.ROTATION_90: {
+ return 360f - 90f;
+ }
+ case Surface.ROTATION_180: {
+ return 360f - 180f;
+ }
+ case Surface.ROTATION_270: {
+ return 360f - 270f;
+ } default: {
+ return 0;
+ }
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mConnectionId != CONNECTION_ID_UNDEFINED;
+ }
+
+ private void throwIfConnectedLocked() {
+ if (mConnectionId != CONNECTION_ID_UNDEFINED) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
+
+ public IAccessibilityServiceClientImpl(Looper looper) {
+ super(null, looper, new Callbacks() {
+ @Override
+ public void onSetConnectionId(int connectionId) {
+ synchronized (mLock) {
+ mConnectionId = connectionId;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceConnected() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean onGesture(int gestureId) {
+ /* do nothing */
+ return false;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ mLastEventTimeMillis = event.getEventTime();
+ if (mWaitingForEventDelivery) {
+ mEventQueue.add(AccessibilityEvent.obtain(event));
+ }
+ mLock.notifyAll();
+ }
+ // Calling out only without a lock held.
+ final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
+ if (listener != null) {
+ listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
new file mode 100644
index 0000000..9b5857f
--- /dev/null
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -0,0 +1,239 @@
+/*
+ * 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.Surface;
+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 Surface.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!");
+ }
+ }
+}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index ba82d79..81c25d8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -16,8 +16,8 @@
package android.view;
-import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
-
+import android.app.ActivityThread;
+import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
@@ -34,6 +34,7 @@ import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -49,7 +50,7 @@ import java.util.Map;
*/
final class AccessibilityInteractionController {
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
private final Handler mHandler;
@@ -69,6 +70,8 @@ final class AccessibilityInteractionController {
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
+ private AddNodeInfosForViewId mAddNodeInfosForViewId;
+
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
mMyLooperThreadId = looper.getThread().getId();
@@ -135,8 +138,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
root = mViewRootImpl.mView;
@@ -148,7 +150,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
@@ -161,19 +163,19 @@ final class AccessibilityInteractionController {
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+ String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
- message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = SomeArgs.obtain();
- args.argi1 = viewId;
- args.argi2 = interactionId;
+ args.argi1 = interactionId;
args.arg1 = callback;
args.arg2 = spec;
+ args.arg3 = viewId;
message.obj = args;
@@ -189,26 +191,26 @@ final class AccessibilityInteractionController {
}
}
- private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
final int flags = message.arg1;
final int accessibilityViewId = message.arg2;
SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
+ final int interactionId = args.argi1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final String viewId = (String) args.arg3;
args.recycle();
- AccessibilityNodeInfo info = null;
+ final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -216,19 +218,31 @@ final class AccessibilityInteractionController {
root = mViewRootImpl.mView;
}
if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && isShown(target)) {
- info = target.createAccessibilityNodeInfo();
+ int resolvedViewId = root.getContext().getResources().getIdentifier(
+ viewId, "id", root.getContext().getPackageName());
+ if (resolvedViewId <= 0) {
+ resolvedViewId = ((Context) ActivityThread.currentActivityThread()
+ .getSystemContext()).getResources()
+ .getIdentifier(viewId, "id", "android");
}
+ if (resolvedViewId <= 0) {
+ return;
+ }
+ if (mAddNodeInfosForViewId == null) {
+ mAddNodeInfosForViewId = new AddNodeInfosForViewId();
+ }
+ mAddNodeInfosForViewId.init(resolvedViewId, infos);
+ root.findViewByPredicate(mAddNodeInfosForViewId);
+ mAddNodeInfosForViewId.reset();
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
}
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
@@ -281,8 +295,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -325,7 +338,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
@@ -384,8 +397,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -426,7 +438,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
if (spec != null) {
spec.recycle();
@@ -484,8 +496,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -500,7 +511,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
if (spec != null) {
spec.recycle();
@@ -561,8 +572,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
target = findViewByAccessibilityId(accessibilityViewId);
@@ -580,7 +590,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -690,20 +700,20 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempViewList = new ArrayList<View>();
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
List<AccessibilityNodeInfo> outInfos) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfRealNode(view, outInfos);
}
}
@@ -711,13 +721,13 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
@@ -920,7 +930,7 @@ final class AccessibilityInteractionController {
private class PrivateHandler extends Handler {
private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
private final static int MSG_FIND_FOCUS = 5;
private final static int MSG_FOCUS_SEARCH = 6;
@@ -937,8 +947,8 @@ final class AccessibilityInteractionController {
return "MSG_PERFORM_ACCESSIBILITY_ACTION";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_FIND_FOCUS:
@@ -960,8 +970,8 @@ final class AccessibilityInteractionController {
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
perfromAccessibilityActionUiThread(message);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- findAccessibilityNodeInfoByViewIdUiThread(message);
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: {
+ findAccessibilityNodeInfosByViewIdUiThread(message);
} break;
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
findAccessibilityNodeInfosByTextUiThread(message);
@@ -977,4 +987,27 @@ final class AccessibilityInteractionController {
}
}
}
+
+ private final class AddNodeInfosForViewId implements Predicate<View> {
+ private int mViewId = View.NO_ID;
+ private List<AccessibilityNodeInfo> mInfos;
+
+ public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+ mViewId = viewId;
+ mInfos = infos;
+ }
+
+ public void reset() {
+ mViewId = View.NO_ID;
+ mInfos = null;
+ }
+
+ @Override
+ public boolean apply(View view) {
+ if (view.getId() == mViewId && isShown(view)) {
+ mInfos.add(view.createAccessibilityNodeInfo());
+ }
+ return false;
+ }
+ }
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1ee2bb3..a9ad97f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -190,6 +190,13 @@ interface IWindowManager
void thawRotation();
/**
+ * Gets whether the rotation is frozen.
+ *
+ * @return Whether the rotation is frozen.
+ */
+ boolean isRotationFrozen();
+
+ /**
* Create a screenshot of the applications currently displayed.
*/
Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a4e4f37..b9babdc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.ActivityThread;
import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
@@ -5007,6 +5008,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (label != null) {
info.setLabeledBy(label);
}
+
+ if ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+ String viewId = null;
+ try {
+ viewId = getResources().getResourceName(mID);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ if (viewId == null) {
+ try {
+ viewId = ((Context) ActivityThread.currentActivityThread()
+ .getSystemContext()).getResources().getResourceName(mID);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ }
+ info.setViewId(viewId);
+ }
}
if (mLabelForId != View.NO_ID) {
@@ -6838,7 +6858,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility();
+ return (mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || isImportantForAccessibility();
}
return false;
}
@@ -18004,10 +18026,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int mAccessibilityWindowId = View.NO_ID;
/**
- * Whether to ingore not exposed for accessibility Views when
- * reporting the view tree to accessibility services.
+ * Flags related to accessibility processing.
+ *
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
- boolean mIncludeNotImportantViews;
+ int mAccessibilityFetchFlags;
/**
* The drawable for highlighting accessibility focus.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3faac40..46f76b8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5481,15 +5481,16 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid,
- spec);
+ .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+ viewId, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 9377cfa..02be4db 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1035,6 +1035,16 @@ public interface WindowManagerPolicy {
public void keepScreenOnStoppedLw();
/**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see WindowManagerPolicy#USER_ROTATION_LOCKED
+ * @see WindowManagerPolicy#USER_ROTATION_FREE
+ */
+ public int getUserRotationMode();
+
+ /**
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
* @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 67df684..84d7e72 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -230,23 +230,25 @@ public final class AccessibilityInteractionClient
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param viewId The id of the view.
- * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
*/
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
- int accessibilityWindowId, long accessibilityNodeId, int viewId) {
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String viewId) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final boolean success =connection.findAccessibilityNodeInfoByViewId(
+ final boolean success = connection.findAccessibilityNodeInfosByViewId(
accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
Thread.currentThread().getId());
if (success) {
- AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
- return info;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -259,7 +261,7 @@ public final class AccessibilityInteractionClient
+ " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
}
}
- return null;
+ return Collections.emptyList();
}
/**
@@ -291,8 +293,10 @@ public final class AccessibilityInteractionClient
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
- return infos;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6c03280..e3d14ec 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
@@ -78,7 +79,10 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
/** @hide */
- public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
// Actions.
@@ -375,6 +379,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mClassName;
private CharSequence mText;
private CharSequence mContentDescription;
+ private CharSequence mViewId;
private final SparseLongArray mChildNodeIds = new SparseLongArray();
private int mActions;
@@ -729,6 +734,37 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
@@ -1373,6 +1409,38 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param viewId The id resource name.
+ */
+ public void setViewId(CharSequence viewId) {
+ enforceNotSealed();
+ mViewId = viewId;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public CharSequence getViewId() {
+ return mViewId;
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
@@ -1614,6 +1682,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeCharSequence(mClassName);
parcel.writeCharSequence(mText);
parcel.writeCharSequence(mContentDescription);
+ parcel.writeCharSequence(mViewId);
// Since instances of this class are fetched via synchronous i.e. blocking
// calls in IPCs we always recycle as soon as the instance is marshaled.
@@ -1639,6 +1708,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = other.mClassName;
mText = other.mText;
mContentDescription = other.mContentDescription;
+ mViewId = other.mViewId;
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
@@ -1689,6 +1759,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = parcel.readCharSequence();
mText = parcel.readCharSequence();
mContentDescription = parcel.readCharSequence();
+ mViewId = parcel.readCharSequence();
}
/**
@@ -1711,6 +1782,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = null;
mText = null;
mContentDescription = null;
+ mViewId = null;
mActions = 0;
}
@@ -1855,6 +1927,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; className: ").append(mClassName);
builder.append("; text: ").append(mText);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewId: ").append(mViewId);
builder.append("; checkable: ").append(isCheckable());
builder.append("; checked: ").append(isChecked());
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index c313b07..8d15472 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -33,9 +33,9 @@ oneway interface IAccessibilityInteractionConnection {
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid, in MagnificationSpec spec);
- void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid, in MagnificationSpec spec);
+ void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, in MagnificationSpec spec);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 57bf0d3..396fd68 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1374,9 +1374,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (isEnabled()) {
if (getFirstVisiblePosition() > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
}
if (getLastVisiblePosition() < getCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
}
}
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index b5cbdd1..e28dac0 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4134,6 +4134,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
}
+ public int getUserRotationMode() {
+ return Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.USER_ROTATION, WindowManagerPolicy.USER_ROTATION_FREE,
+ UserHandle.USER_CURRENT);
+ }
// User rotation: to be used when all else fails in assigning an orientation to the device
public void setUserRotationMode(int mode, int rot) {
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0725df0..b7c3450 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -518,6 +517,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
ComponentName componentName = new ComponentName("foo.bar",
"AutomationAccessibilityService");
synchronized (mLock) {
+ if (mUiAutomationService != null) {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + "already registered!");
+ }
// If an automation services is connected to the system all services are stopped
// so the automation one is the only one running. Settings are not changed so when
// the automation service goes away the state is restored from the settings.
@@ -556,7 +559,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
// Stash the old state so we can restore it when the keyguard is gone.
- mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId, getCurrentUserStateLocked());
+ mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId,
+ getCurrentUserStateLocked());
// Set the temporary state.
userState.mIsAccessibilityEnabled = true;
userState.mIsTouchExplorationEnabled= touchExplorationEnabled;
@@ -579,6 +583,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
&& serviceClient != null && mUiAutomationService.mServiceInterface
.asBinder() == serviceClient.asBinder()) {
mUiAutomationService.binderDied();
+ } else {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + " not registered!");
}
}
}
@@ -935,7 +942,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
if (!event.isImportantForAccessibility()
- && !service.mIncludeNotImportantViews) {
+ && (service.mFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
return false;
}
@@ -1486,7 +1494,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
boolean mRequestTouchExplorationMode;
- boolean mIncludeNotImportantViews;
+ int mFetchFlags;
long mNotificationTimeout;
@@ -1565,10 +1573,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.JELLY_BEAN) {
- mIncludeNotImportantViews =
- (info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mFetchFlags |= (info.flags
+ & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 ?
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
}
+ mFetchFlags |= (info.flags
+ & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0 ?
+ AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS : 0;
+
mRequestTouchExplorationMode = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
@@ -1664,8 +1677,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
@Override
- public boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId, int interactionId,
+ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewIdResName, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
@@ -1689,14 +1702,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
- connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+ connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+ viewIdResName, interactionId, callback, mFetchFlags, interrogatingPid,
+ interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1735,14 +1747,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
- interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
+ spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1781,15 +1792,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int allFlags = flags | ((mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0);
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- interactionId, callback, allFlags, interrogatingPid, interrogatingTid,
- spec);
+ interactionId, callback, mFetchFlags | flags, interrogatingPid,
+ interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1828,14 +1837,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findFocus(accessibilityNodeId, focusType, interactionId, callback,
- flags, interrogatingPid, interrogatingTid, spec);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1874,14 +1881,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.focusSearch(accessibilityNodeId, direction, interactionId, callback,
- flags, interrogatingPid, interrogatingTid, spec);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1920,13 +1925,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d8e199b..497b3ec 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -51,6 +51,7 @@ import android.app.IProcessObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.IThumbnailReceiver;
+import android.app.IUiAutomationConnection;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
import android.app.Notification;
@@ -162,7 +163,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
-public final class ActivityManagerService extends ActivityManagerNative
+public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
private static final String USER_DATA_DIR = "/data/user/";
static final String TAG = "ActivityManager";
@@ -4259,8 +4260,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
thread.bindApplication(processName, appInfo, providers,
app.instrumentationClass, profileFile, profileFd, profileAutoStop,
- app.instrumentationArguments, app.instrumentationWatcher, testMode,
- enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
+ app.instrumentationArguments, app.instrumentationWatcher,
+ app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
+ isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false);
@@ -12147,7 +12149,8 @@ public final class ActivityManagerService extends ActivityManagerNative
public boolean startInstrumentation(ComponentName className,
String profileFile, int flags, Bundle arguments,
- IInstrumentationWatcher watcher, int userId) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
+ int userId) {
enforceNotIsolatedCaller("startInstrumentation");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, true, "startInstrumentation", null);
@@ -12201,6 +12204,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.instrumentationProfileFile = profileFile;
app.instrumentationArguments = arguments;
app.instrumentationWatcher = watcher;
+ app.instrumentationUiAutomationConnection = uiAutomationConnection;
app.instrumentationResultClass = className;
Binder.restoreCallingIdentity(origId);
}
@@ -12243,7 +12247,15 @@ public final class ActivityManagerService extends ActivityManagerNative
} catch (RemoteException e) {
}
}
+ if (app.instrumentationUiAutomationConnection != null) {
+ try {
+ app.instrumentationUiAutomationConnection.shutdown();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
app.instrumentationWatcher = null;
+ app.instrumentationUiAutomationConnection = null;
app.instrumentationClass = null;
app.instrumentationInfo = null;
app.instrumentationProfileFile = null;
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 7fbab04..a32af2f 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@ import android.app.ActivityManager;
import android.app.Dialog;
import android.app.IApplicationThread;
import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -95,6 +96,7 @@ class ProcessRecord {
ApplicationInfo instrumentationInfo; // the application being instrumented
String instrumentationProfileFile; // where to save profiling
IInstrumentationWatcher instrumentationWatcher; // who is waiting
+ IUiAutomationConnection instrumentationUiAutomationConnection; // Connection to use the UI introspection APIs.
Bundle instrumentationArguments;// as given to us
ComponentName instrumentationResultClass;// copy of instrumentationClass
boolean usingWrapper; // Set to true when process was launched with a wrapper attached
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index ff2dc0f..8d44f36 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5598,6 +5598,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public boolean isRotationFrozen() {
+ return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
+ }
+
+ @Override
public int watchRotation(IRotationWatcher watcher) {
final IBinder watcherBinder = watcher.asBinder();
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
index 30876d0..aa7c677 100644
--- a/test-runner/src/android/test/AndroidTestRunner.java
+++ b/test-runner/src/android/test/AndroidTestRunner.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.PerformanceCollector.PerformanceResultsWriter;
import com.google.android.collect.Lists;
+
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index 8e30875..8e833ca 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -21,6 +21,7 @@ import com.android.internal.util.Predicates;
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.UiAutomation;
import android.os.Bundle;
import android.os.Debug;
import android.os.Looper;
@@ -390,12 +391,11 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu
}
/**
- * Get the Bundle object that contains the arguments
+ * Get the arguments passed to this instrumentation.
*
* @return the Bundle object
- * @hide
*/
- public Bundle getBundle(){
+ protected Bundle getArguments() {
return mArguments;
}