Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80943d8d authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Svetoslav
Browse files

Adding UI test automation APIs.

This change adds APIs support for implementing UI tests. Such tests do
not rely on internal application structure and can span across application
boundaries. UI automation APIs are encapsulated in the UiAutomation object
that is provided by an Instrumentation object. It is initialized by the
system and can be used for both introspecting the screen and performing
interactions simulating a user. UI test are normal instrumentation tests
and are executed on the device.

UiAutomation uses the accessibility APIs to introspect the screen and
a special delegate object to perform privileged operations such as
injecting input events. Since instrumentation tests are invoked by a shell
command, the shell program launching the tests creates a delegate object and
passes it as an argument to started instrumentation. This delegate
allows the APK that runs the tests to access some privileged operations
protected by a signature level permissions which are explicitly granted
to the shell user.

The UiAutomation object also supports running tests in the legacy way
where the tests are run as a Java shell program. This enables existing
UiAutomator tests to keep working while the new ones should be implemented
using the new APIs. The UiAutomation object exposes lower level APIs which
allow simulation of arbitrary user interactions and writing complete UI test
cases. Clients, such as UiAutomator, are encouraged to implement higher-
level APIs which minimize development effort and can be used as a helper
library by the test developer.

The benefit of this change is decoupling UiAutomator from the system
since the former was calling hidden APIs which required that it is
bundled in the system image. This prevented UiAutomator from being
evolved separately from the system. Also UiAutomator was creating
additional API surface in the system image. Another benefit of the new
design is that now test cases have access to a context and can use
public platform APIs in addition to the UiAutomator ones. Further,
third-parties can develop their own higher level test APIs on top
of the lower level ones exposes by UiAutomation.

bug:8028258

Also this change adds the fully qualified resource name of the view's
id in the emitted AccessibilityNodeInfo if a special flag is set while
configuring the accessibility service. Also added is API for looking
up node infos by this id. The id resource name is relatively more stable
compared to the generaed id number which may change from one build to
another. This API facilitate reuing the already defined ids for UI
automation.

bug:7678973

Change-Id: I589ad14790320dec8a33095953926c2a2dd0228b
parent 64cae1a6
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ 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/IWallpaperManager.aidl \
+26 −0
Original line number Diff line number Diff line
@@ -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
+5 −1
Original line number Diff line number Diff line
@@ -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());
        }

+7 −2
Original line number Diff line number Diff line
@@ -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;
+10 −0
Original line number Diff line number Diff line
@@ -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;
@@ -151,6 +152,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>
Loading