summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 f45f9779c9be..1fb6b52d8fc8 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 d367a00bc347..43c5b0f45b2f 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 add7a23e4138..b5574cf9180b 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 7efe189fe015..c5f51dc25a2e 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 75a4f838ac1b..2006bc73f807 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 f33f503ef1ff..7a29f359678b 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 6837386e3af6..000000000000
--- 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 61b206756c68..ee126f440301 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 127164567e51..5478bb75e925 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 63aa5f9b5f34..386e8d7b641c 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 fd4389e594a2..e03d3fd437fe 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 8af17a49ffdb..9b4fe611c8ea 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 03a26d4274c4..5dbbab4c97dd 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 000000000000..09bf8296777d
--- /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 39186c654aae..a2eeddde6bee 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 000000000000..e611f6da95df
--- /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 000000000000..9b5857ff2e94
--- /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 ba82d7920ab4..81c25d8bbc01 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 1ee2bb3cfc5c..a9ad97f61ed6 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 a4e4f37927a8..b9babdcc2e03 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 3faac40d6ca3..46f76b856aa0 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 9377cfaab42c..02be4db477d1 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 67df684c0777..84d7e720b31f 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 6c032804d088..e3d14ec5d567 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 c313b0798e3f..8d15472f0999 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 57bf0d3aee92..396fd6821cde 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 b5cbdd1fd2f7..e28dac05730f 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 0725df069d39..b7c3450bcafd 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 d8e199bafeba..497b3ec51c89 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 7fbab044546f..a32af2f8c020 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 ff2dc0f6abd9..8d44f366b003 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 30876d0974f7..aa7c677c968f 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 8e308758ec66..8e833ca10062 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;
}