am 07bba9ab: Merge "shell based UI Automator source move" into lmp-mr1-dev

* commit '07bba9abeeba77cdf491ecc3047d1fa34f280bbb':
  shell based UI Automator source move
This commit is contained in:
Guang Zhu
2014-12-14 03:39:07 +00:00
committed by Android Git Automerger
66 changed files with 10078 additions and 0 deletions

View File

@ -0,0 +1,25 @@
#
# 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.
#
# don't build uiautomator in unbundled env
ifndef TARGET_BUILD_APPS
include $(call all-subdir-makefiles)
else
ifneq ($(filter uiautomator,$(TARGET_BUILD_APPS)),)
# used by the platform apps build.
include $(call all-subdir-makefiles)
endif
endif

View File

174
cmds/uiautomator/api/16.txt Normal file
View File

@ -0,0 +1,174 @@
package com.android.uiautomator.core {
public class UiCollection extends com.android.uiautomator.core.UiObject {
ctor public UiCollection(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByInstance(com.android.uiautomator.core.UiSelector, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount(com.android.uiautomator.core.UiSelector);
}
public class UiDevice {
method public void clearLastTraversedText();
method public boolean click(int, int);
method public void dumpWindowHierarchy(java.lang.String);
method public void freezeRotation() throws android.os.RemoteException;
method public java.lang.String getCurrentActivityName();
method public java.lang.String getCurrentPackageName();
method public int getDisplayHeight();
method public int getDisplayWidth();
method public static com.android.uiautomator.core.UiDevice getInstance();
method public java.lang.String getLastTraversedText();
method public boolean hasAnyWatcherTriggered();
method public boolean hasWatcherTriggered(java.lang.String);
method public boolean isScreenOn() throws android.os.RemoteException;
method public boolean pressBack();
method public boolean pressDPadCenter();
method public boolean pressDPadDown();
method public boolean pressDPadLeft();
method public boolean pressDPadRight();
method public boolean pressDPadUp();
method public boolean pressDelete();
method public boolean pressEnter();
method public boolean pressHome();
method public boolean pressKeyCode(int);
method public boolean pressKeyCode(int, int);
method public boolean pressMenu();
method public boolean pressRecentApps() throws android.os.RemoteException;
method public boolean pressSearch();
method public void registerWatcher(java.lang.String, com.android.uiautomator.core.UiWatcher);
method public void removeWatcher(java.lang.String);
method public void resetWatcherTriggers();
method public void runWatchers();
method public void sleep() throws android.os.RemoteException;
method public boolean swipe(int, int, int, int, int);
method public boolean swipe(android.graphics.Point[], int);
method public void unfreezeRotation() throws android.os.RemoteException;
method public void waitForIdle();
method public void waitForIdle(long);
method public boolean waitForWindowUpdate(java.lang.String, long);
method public void wakeUp() throws android.os.RemoteException;
}
public class UiObject {
ctor public UiObject(com.android.uiautomator.core.UiSelector);
method public void clearTextField() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean click() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow(long) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean exists();
method protected android.view.accessibility.AccessibilityNodeInfo findAccessibilityNodeInfo(long);
method public android.graphics.Rect getBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChild(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getContentDescription() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getFromParent(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getPackageName() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public final com.android.uiautomator.core.UiSelector getSelector();
method public java.lang.String getText() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isCheckable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isChecked() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isEnabled() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocusable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocused() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isLongClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isScrollable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isSelected() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClick() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean setText(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeDown(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeLeft(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeRight(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeUp(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean waitForExists(long);
method public boolean waitUntilGone(long);
field protected static final int SWIPE_MARGIN_LIMIT = 5; // 0x5
field protected static final long WAIT_FOR_SELECTOR_POLL = 1000L; // 0x3e8L
field protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10000L; // 0x2710L
field protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500L; // 0x157cL
}
public class UiObjectNotFoundException extends java.lang.Exception {
ctor public UiObjectNotFoundException(java.lang.String);
ctor public UiObjectNotFoundException(java.lang.String, java.lang.Throwable);
ctor public UiObjectNotFoundException(java.lang.Throwable);
}
public class UiScrollable extends com.android.uiautomator.core.UiCollection {
ctor public UiScrollable(com.android.uiautomator.core.UiSelector);
method protected boolean exists(com.android.uiautomator.core.UiSelector);
method public boolean flingBackward();
method public boolean flingForward();
method public boolean flingToBeginning(int);
method public boolean flingToEnd(int);
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getMaxSearchSwipes();
method public double getSwipeDeadZonePercentage();
method public boolean scrollBackward();
method public boolean scrollBackward(int);
method public boolean scrollDescriptionIntoView(java.lang.String);
method public boolean scrollForward();
method public boolean scrollForward(int);
method public boolean scrollIntoView(com.android.uiautomator.core.UiSelector);
method public boolean scrollTextIntoView(java.lang.String);
method public boolean scrollToBeginning(int, int);
method public boolean scrollToBeginning(int);
method public boolean scrollToEnd(int, int);
method public boolean scrollToEnd(int);
method public void setAsHorizontalList();
method public void setAsVerticalList();
method public void setMaxSearchSwipes(int);
method public void setSwipeDeadZonePercentage(double);
}
public class UiSelector {
ctor public UiSelector();
method public com.android.uiautomator.core.UiSelector checked(boolean);
method public com.android.uiautomator.core.UiSelector childSelector(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector className(java.lang.String);
method public com.android.uiautomator.core.UiSelector clickable(boolean);
method public com.android.uiautomator.core.UiSelector description(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionStartsWith(java.lang.String);
method public com.android.uiautomator.core.UiSelector enabled(boolean);
method public com.android.uiautomator.core.UiSelector focusable(boolean);
method public com.android.uiautomator.core.UiSelector focused(boolean);
method public com.android.uiautomator.core.UiSelector fromParent(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector index(int);
method public com.android.uiautomator.core.UiSelector instance(int);
method public com.android.uiautomator.core.UiSelector packageName(java.lang.String);
method public com.android.uiautomator.core.UiSelector scrollable(boolean);
method public com.android.uiautomator.core.UiSelector selected(boolean);
method public com.android.uiautomator.core.UiSelector text(java.lang.String);
method public com.android.uiautomator.core.UiSelector textContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector textStartsWith(java.lang.String);
}
public abstract interface UiWatcher {
method public abstract boolean checkForCondition();
}
}
package com.android.uiautomator.testrunner {
public abstract interface IAutomationSupport {
method public abstract void sendStatus(int, android.os.Bundle);
}
public class UiAutomatorTestCase extends junit.framework.TestCase {
ctor public UiAutomatorTestCase();
method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
method public android.os.Bundle getParams();
method public com.android.uiautomator.core.UiDevice getUiDevice();
method public void sleep(long);
}
}

192
cmds/uiautomator/api/17.txt Normal file
View File

@ -0,0 +1,192 @@
package com.android.uiautomator.core {
public class UiCollection extends com.android.uiautomator.core.UiObject {
ctor public UiCollection(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByInstance(com.android.uiautomator.core.UiSelector, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount(com.android.uiautomator.core.UiSelector);
}
public class UiDevice {
method public void clearLastTraversedText();
method public boolean click(int, int);
method public void dumpWindowHierarchy(java.lang.String);
method public void freezeRotation() throws android.os.RemoteException;
method public deprecated java.lang.String getCurrentActivityName();
method public java.lang.String getCurrentPackageName();
method public int getDisplayHeight();
method public int getDisplayRotation();
method public int getDisplayWidth();
method public static com.android.uiautomator.core.UiDevice getInstance();
method public java.lang.String getLastTraversedText();
method public java.lang.String getProductName();
method public boolean hasAnyWatcherTriggered();
method public boolean hasWatcherTriggered(java.lang.String);
method public boolean isNaturalOrientation();
method public boolean isScreenOn() throws android.os.RemoteException;
method public boolean pressBack();
method public boolean pressDPadCenter();
method public boolean pressDPadDown();
method public boolean pressDPadLeft();
method public boolean pressDPadRight();
method public boolean pressDPadUp();
method public boolean pressDelete();
method public boolean pressEnter();
method public boolean pressHome();
method public boolean pressKeyCode(int);
method public boolean pressKeyCode(int, int);
method public boolean pressMenu();
method public boolean pressRecentApps() throws android.os.RemoteException;
method public boolean pressSearch();
method public void registerWatcher(java.lang.String, com.android.uiautomator.core.UiWatcher);
method public void removeWatcher(java.lang.String);
method public void resetWatcherTriggers();
method public void runWatchers();
method public void setOrientationLeft() throws android.os.RemoteException;
method public void setOrientationNatural() throws android.os.RemoteException;
method public void setOrientationRight() throws android.os.RemoteException;
method public void sleep() throws android.os.RemoteException;
method public boolean swipe(int, int, int, int, int);
method public boolean swipe(android.graphics.Point[], int);
method public boolean takeScreenshot(java.io.File);
method public boolean takeScreenshot(java.io.File, float, int);
method public void unfreezeRotation() throws android.os.RemoteException;
method public void waitForIdle();
method public void waitForIdle(long);
method public boolean waitForWindowUpdate(java.lang.String, long);
method public void wakeUp() throws android.os.RemoteException;
}
public class UiObject {
ctor public UiObject(com.android.uiautomator.core.UiSelector);
method public void clearTextField() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean click() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow(long) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean exists();
method protected android.view.accessibility.AccessibilityNodeInfo findAccessibilityNodeInfo(long);
method public android.graphics.Rect getBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChild(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getContentDescription() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getFromParent(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getPackageName() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public final com.android.uiautomator.core.UiSelector getSelector();
method public java.lang.String getText() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public android.graphics.Rect getVisibleBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isCheckable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isChecked() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isEnabled() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocusable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocused() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isLongClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isScrollable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isSelected() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClick() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean setText(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeDown(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeLeft(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeRight(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeUp(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean waitForExists(long);
method public boolean waitUntilGone(long);
field protected static final int SWIPE_MARGIN_LIMIT = 5; // 0x5
field protected static final long WAIT_FOR_EVENT_TMEOUT = 3000L; // 0xbb8L
field protected static final long WAIT_FOR_SELECTOR_POLL = 1000L; // 0x3e8L
field protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10000L; // 0x2710L
field protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500L; // 0x157cL
}
public class UiObjectNotFoundException extends java.lang.Exception {
ctor public UiObjectNotFoundException(java.lang.String);
ctor public UiObjectNotFoundException(java.lang.String, java.lang.Throwable);
ctor public UiObjectNotFoundException(java.lang.Throwable);
}
public class UiScrollable extends com.android.uiautomator.core.UiCollection {
ctor public UiScrollable(com.android.uiautomator.core.UiSelector);
method protected boolean exists(com.android.uiautomator.core.UiSelector);
method public boolean flingBackward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingForward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingToBeginning(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingToEnd(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getMaxSearchSwipes();
method public double getSwipeDeadZonePercentage();
method public boolean scrollBackward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollBackward(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollDescriptionIntoView(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollForward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollForward(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollIntoView(com.android.uiautomator.core.UiObject) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollIntoView(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollTextIntoView(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToBeginning(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToBeginning(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToEnd(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToEnd(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiScrollable setAsHorizontalList();
method public com.android.uiautomator.core.UiScrollable setAsVerticalList();
method public com.android.uiautomator.core.UiScrollable setMaxSearchSwipes(int);
method public com.android.uiautomator.core.UiScrollable setSwipeDeadZonePercentage(double);
}
public class UiSelector {
ctor public UiSelector();
method public com.android.uiautomator.core.UiSelector checked(boolean);
method public com.android.uiautomator.core.UiSelector childSelector(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector className(java.lang.String);
method public com.android.uiautomator.core.UiSelector className(java.lang.Class<T>);
method public com.android.uiautomator.core.UiSelector classNameMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector clickable(boolean);
method protected com.android.uiautomator.core.UiSelector cloneSelector();
method public com.android.uiautomator.core.UiSelector description(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionStartsWith(java.lang.String);
method public com.android.uiautomator.core.UiSelector enabled(boolean);
method public com.android.uiautomator.core.UiSelector focusable(boolean);
method public com.android.uiautomator.core.UiSelector focused(boolean);
method public com.android.uiautomator.core.UiSelector fromParent(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector index(int);
method public com.android.uiautomator.core.UiSelector instance(int);
method public com.android.uiautomator.core.UiSelector longClickable(boolean);
method public com.android.uiautomator.core.UiSelector packageName(java.lang.String);
method public com.android.uiautomator.core.UiSelector packageNameMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector scrollable(boolean);
method public com.android.uiautomator.core.UiSelector selected(boolean);
method public com.android.uiautomator.core.UiSelector text(java.lang.String);
method public com.android.uiautomator.core.UiSelector textContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector textMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector textStartsWith(java.lang.String);
}
public abstract interface UiWatcher {
method public abstract boolean checkForCondition();
}
}
package com.android.uiautomator.testrunner {
public abstract interface IAutomationSupport {
method public abstract void sendStatus(int, android.os.Bundle);
}
public class UiAutomatorTestCase extends junit.framework.TestCase {
ctor public UiAutomatorTestCase();
method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
method public android.os.Bundle getParams();
method public com.android.uiautomator.core.UiDevice getUiDevice();
method public void sleep(long);
}
}

View File

@ -0,0 +1,222 @@
package com.android.uiautomator.core {
public final class Configurator {
method public long getActionAcknowledgmentTimeout();
method public static com.android.uiautomator.core.Configurator getInstance();
method public long getKeyInjectionDelay();
method public long getScrollAcknowledgmentTimeout();
method public long getWaitForIdleTimeout();
method public long getWaitForSelectorTimeout();
method public com.android.uiautomator.core.Configurator setActionAcknowledgmentTimeout(long);
method public com.android.uiautomator.core.Configurator setKeyInjectionDelay(long);
method public com.android.uiautomator.core.Configurator setScrollAcknowledgmentTimeout(long);
method public com.android.uiautomator.core.Configurator setWaitForIdleTimeout(long);
method public com.android.uiautomator.core.Configurator setWaitForSelectorTimeout(long);
}
public class UiCollection extends com.android.uiautomator.core.UiObject {
ctor public UiCollection(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByInstance(com.android.uiautomator.core.UiSelector, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount(com.android.uiautomator.core.UiSelector);
}
public class UiDevice {
method public void clearLastTraversedText();
method public boolean click(int, int);
method public boolean drag(int, int, int, int, int);
method public void dumpWindowHierarchy(java.lang.String);
method public void freezeRotation() throws android.os.RemoteException;
method public deprecated java.lang.String getCurrentActivityName();
method public java.lang.String getCurrentPackageName();
method public int getDisplayHeight();
method public int getDisplayRotation();
method public android.graphics.Point getDisplaySizeDp();
method public int getDisplayWidth();
method public static com.android.uiautomator.core.UiDevice getInstance();
method public java.lang.String getLastTraversedText();
method public java.lang.String getProductName();
method public boolean hasAnyWatcherTriggered();
method public boolean hasWatcherTriggered(java.lang.String);
method public boolean isNaturalOrientation();
method public boolean isScreenOn() throws android.os.RemoteException;
method public boolean openNotification();
method public boolean openQuickSettings();
method public boolean pressBack();
method public boolean pressDPadCenter();
method public boolean pressDPadDown();
method public boolean pressDPadLeft();
method public boolean pressDPadRight();
method public boolean pressDPadUp();
method public boolean pressDelete();
method public boolean pressEnter();
method public boolean pressHome();
method public boolean pressKeyCode(int);
method public boolean pressKeyCode(int, int);
method public boolean pressMenu();
method public boolean pressRecentApps() throws android.os.RemoteException;
method public boolean pressSearch();
method public void registerWatcher(java.lang.String, com.android.uiautomator.core.UiWatcher);
method public void removeWatcher(java.lang.String);
method public void resetWatcherTriggers();
method public void runWatchers();
method public void setCompressedLayoutHeirarchy(boolean);
method public void setOrientationLeft() throws android.os.RemoteException;
method public void setOrientationNatural() throws android.os.RemoteException;
method public void setOrientationRight() throws android.os.RemoteException;
method public void sleep() throws android.os.RemoteException;
method public boolean swipe(int, int, int, int, int);
method public boolean swipe(android.graphics.Point[], int);
method public boolean takeScreenshot(java.io.File);
method public boolean takeScreenshot(java.io.File, float, int);
method public void unfreezeRotation() throws android.os.RemoteException;
method public void waitForIdle();
method public void waitForIdle(long);
method public boolean waitForWindowUpdate(java.lang.String, long);
method public void wakeUp() throws android.os.RemoteException;
}
public class UiObject {
ctor public UiObject(com.android.uiautomator.core.UiSelector);
method public void clearTextField() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean click() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickAndWaitForNewWindow(long) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean clickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean dragTo(com.android.uiautomator.core.UiObject, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean dragTo(int, int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean exists();
method protected android.view.accessibility.AccessibilityNodeInfo findAccessibilityNodeInfo(long);
method public android.graphics.Rect getBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChild(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getChildCount() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getClassName() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getContentDescription() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getFromParent(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public java.lang.String getPackageName() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public final com.android.uiautomator.core.UiSelector getSelector();
method public java.lang.String getText() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public android.graphics.Rect getVisibleBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isCheckable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isChecked() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isEnabled() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocusable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isFocused() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isLongClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isScrollable() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean isSelected() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClick() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean longClickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean performMultiPointerGesture(android.view.MotionEvent.PointerCoords...);
method public boolean performTwoPointerGesture(android.graphics.Point, android.graphics.Point, android.graphics.Point, android.graphics.Point, int);
method public boolean pinchIn(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean pinchOut(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean setText(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeDown(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeLeft(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeRight(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean swipeUp(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean waitForExists(long);
method public boolean waitUntilGone(long);
field protected static final int FINGER_TOUCH_HALF_WIDTH = 20; // 0x14
field protected static final int SWIPE_MARGIN_LIMIT = 5; // 0x5
field protected static final deprecated long WAIT_FOR_EVENT_TMEOUT = 3000L; // 0xbb8L
field protected static final long WAIT_FOR_SELECTOR_POLL = 1000L; // 0x3e8L
field protected static final deprecated long WAIT_FOR_SELECTOR_TIMEOUT = 10000L; // 0x2710L
field protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500L; // 0x157cL
}
public class UiObjectNotFoundException extends java.lang.Exception {
ctor public UiObjectNotFoundException(java.lang.String);
ctor public UiObjectNotFoundException(java.lang.String, java.lang.Throwable);
ctor public UiObjectNotFoundException(java.lang.Throwable);
}
public class UiScrollable extends com.android.uiautomator.core.UiCollection {
ctor public UiScrollable(com.android.uiautomator.core.UiSelector);
method protected boolean exists(com.android.uiautomator.core.UiSelector);
method public boolean flingBackward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingForward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingToBeginning(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean flingToEnd(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByDescription(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiObject getChildByText(com.android.uiautomator.core.UiSelector, java.lang.String, boolean) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public int getMaxSearchSwipes();
method public double getSwipeDeadZonePercentage();
method public boolean scrollBackward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollBackward(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollDescriptionIntoView(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollForward() throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollForward(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollIntoView(com.android.uiautomator.core.UiObject) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollIntoView(com.android.uiautomator.core.UiSelector) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollTextIntoView(java.lang.String) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToBeginning(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToBeginning(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToEnd(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public boolean scrollToEnd(int) throws com.android.uiautomator.core.UiObjectNotFoundException;
method public com.android.uiautomator.core.UiScrollable setAsHorizontalList();
method public com.android.uiautomator.core.UiScrollable setAsVerticalList();
method public com.android.uiautomator.core.UiScrollable setMaxSearchSwipes(int);
method public com.android.uiautomator.core.UiScrollable setSwipeDeadZonePercentage(double);
}
public class UiSelector {
ctor public UiSelector();
method public com.android.uiautomator.core.UiSelector checkable(boolean);
method public com.android.uiautomator.core.UiSelector checked(boolean);
method public com.android.uiautomator.core.UiSelector childSelector(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector className(java.lang.String);
method public com.android.uiautomator.core.UiSelector className(java.lang.Class<T>);
method public com.android.uiautomator.core.UiSelector classNameMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector clickable(boolean);
method protected com.android.uiautomator.core.UiSelector cloneSelector();
method public com.android.uiautomator.core.UiSelector description(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector descriptionStartsWith(java.lang.String);
method public com.android.uiautomator.core.UiSelector enabled(boolean);
method public com.android.uiautomator.core.UiSelector focusable(boolean);
method public com.android.uiautomator.core.UiSelector focused(boolean);
method public com.android.uiautomator.core.UiSelector fromParent(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector index(int);
method public com.android.uiautomator.core.UiSelector instance(int);
method public com.android.uiautomator.core.UiSelector longClickable(boolean);
method public com.android.uiautomator.core.UiSelector packageName(java.lang.String);
method public com.android.uiautomator.core.UiSelector packageNameMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector resourceId(java.lang.String);
method public com.android.uiautomator.core.UiSelector resourceIdMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector scrollable(boolean);
method public com.android.uiautomator.core.UiSelector selected(boolean);
method public com.android.uiautomator.core.UiSelector text(java.lang.String);
method public com.android.uiautomator.core.UiSelector textContains(java.lang.String);
method public com.android.uiautomator.core.UiSelector textMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector textStartsWith(java.lang.String);
}
public abstract interface UiWatcher {
method public abstract boolean checkForCondition();
}
}
package com.android.uiautomator.testrunner {
public abstract interface IAutomationSupport {
method public abstract void sendStatus(int, android.os.Bundle);
}
public class UiAutomatorTestCase extends junit.framework.TestCase {
ctor public UiAutomatorTestCase();
method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
method public android.os.Bundle getParams();
method public com.android.uiautomator.core.UiDevice getUiDevice();
method public void sleep(long);
}
}

View File

View File

@ -0,0 +1,17 @@
#
# 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.
#
include $(call all-subdir-makefiles)

View File

@ -0,0 +1,33 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := uiautomator.core
LOCAL_MODULE := uiautomator
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := uiautomator
LOCAL_SRC_FILES := uiautomator
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)

View File

@ -0,0 +1,109 @@
/*
* 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 com.android.commands.uiautomator;
import android.app.UiAutomation;
import android.graphics.Point;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Environment;
import android.view.Display;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.commands.uiautomator.Launcher.Command;
import com.android.uiautomator.core.AccessibilityNodeInfoDumper;
import com.android.uiautomator.core.UiAutomationShellWrapper;
import java.io.File;
import java.util.concurrent.TimeoutException;
/**
* Implementation of the dump subcommand
*
* This creates an XML dump of current UI hierarchy
*/
public class DumpCommand extends Command {
private static final File DEFAULT_DUMP_FILE = new File(
Environment.getLegacyExternalStorageDirectory(), "window_dump.xml");
public DumpCommand() {
super("dump");
}
@Override
public String shortHelp() {
return "creates an XML dump of current UI hierarchy";
}
@Override
public String detailedOptions() {
return " dump [--verbose][file]\n"
+ " [--compressed]: dumps compressed layout information.\n"
+ " [file]: the location where the dumped XML should be stored, default is\n "
+ DEFAULT_DUMP_FILE.getAbsolutePath() + "\n";
}
@Override
public void run(String[] args) {
File dumpFile = DEFAULT_DUMP_FILE;
boolean verboseMode = true;
for (String arg : args) {
if (arg.equals("--compressed"))
verboseMode = false;
else if (!arg.startsWith("-")) {
dumpFile = new File(arg);
}
}
UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
automationWrapper.connect();
if (verboseMode) {
// default
automationWrapper.setCompressedLayoutHierarchy(false);
} else {
automationWrapper.setCompressedLayoutHierarchy(true);
}
// It appears that the bridge needs time to be ready. Making calls to the
// bridge immediately after connecting seems to cause exceptions. So let's also
// do a wait for idle in case the app is busy.
try {
UiAutomation uiAutomation = automationWrapper.getUiAutomation();
uiAutomation.waitForIdle(1000, 1000 * 10);
AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
if (info == null) {
System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
return;
}
Display display =
DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
int rotation = display.getRotation();
Point size = new Point();
display.getSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y);
} catch (TimeoutException re) {
System.err.println("ERROR: could not get idle state.");
return;
} finally {
automationWrapper.disconnect();
}
System.out.println(
String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath()));
}
}

View File

@ -0,0 +1,75 @@
/*
* 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 com.android.commands.uiautomator;
import android.app.UiAutomation.OnAccessibilityEventListener;
import android.view.accessibility.AccessibilityEvent;
import com.android.commands.uiautomator.Launcher.Command;
import com.android.uiautomator.core.UiAutomationShellWrapper;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Implementation of the events subcommand
*
* Prints out accessibility events until process is stopped.
*/
public class EventsCommand extends Command {
private Object mQuitLock = new Object();
public EventsCommand() {
super("events");
}
@Override
public String shortHelp() {
return "prints out accessibility events until terminated";
}
@Override
public String detailedOptions() {
return null;
}
@Override
public void run(String[] args) {
UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
automationWrapper.connect();
automationWrapper.getUiAutomation().setOnAccessibilityEventListener(
new OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
System.out.println(String.format("%s %s",
formatter.format(new Date()), event.toString()));
}
});
// there's really no way to stop, essentially we just block indefinitely here and wait
// for user to press Ctrl+C
synchronized (mQuitLock) {
try {
mQuitLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
automationWrapper.disconnect();
}
}

View File

@ -0,0 +1,135 @@
/*
* 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 com.android.commands.uiautomator;
import android.os.Process;
import java.util.Arrays;
/**
* Entry point into the uiautomator command line
*
* This class maintains the list of sub commands, and redirect the control into it based on the
* command line arguments. It also prints out help arguments for each sub commands.
*
* To add a new sub command, implement {@link Command} and add an instance into COMMANDS array
*/
public class Launcher {
/**
* A simple abstraction class for supporting generic sub commands
*/
public static abstract class Command {
private String mName;
public Command(String name) {
mName = name;
}
/**
* Returns the name of the sub command
* @return
*/
public String name() {
return mName;
}
/**
* Returns a one-liner of the function of this command
* @return
*/
public abstract String shortHelp();
/**
* Returns a detailed explanation of the command usage
*
* Usage may have multiple lines, indentation of 4 spaces recommended.
* @return
*/
public abstract String detailedOptions();
/**
* Starts the command with the provided arguments
* @param args
*/
public abstract void run(String args[]);
}
public static void main(String[] args) {
// show a meaningful process name in `ps`
Process.setArgV0("uiautomator");
if (args.length >= 1) {
Command command = findCommand(args[0]);
if (command != null) {
String[] args2 = {};
if (args.length > 1) {
// consume the first arg
args2 = Arrays.copyOfRange(args, 1, args.length);
}
command.run(args2);
return;
}
}
HELP_COMMAND.run(args);
}
private static Command findCommand(String name) {
for (Command command : COMMANDS) {
if (command.name().equals(name)) {
return command;
}
}
return null;
}
private static Command HELP_COMMAND = new Command("help") {
@Override
public void run(String[] args) {
System.err.println("Usage: uiautomator <subcommand> [options]\n");
System.err.println("Available subcommands:\n");
for (Command command : COMMANDS) {
String shortHelp = command.shortHelp();
String detailedOptions = command.detailedOptions();
if (shortHelp == null) {
shortHelp = "";
}
if (detailedOptions == null) {
detailedOptions = "";
}
System.err.println(String.format("%s: %s", command.name(), shortHelp));
System.err.println(detailedOptions);
}
}
@Override
public String detailedOptions() {
return null;
}
@Override
public String shortHelp() {
return "displays help message";
}
};
private static Command[] COMMANDS = new Command[] {
HELP_COMMAND,
new RunTestCommand(),
new DumpCommand(),
new EventsCommand(),
};
}

View File

@ -0,0 +1,258 @@
/*
* 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 com.android.commands.uiautomator;
import android.os.Bundle;
import android.util.Log;
import com.android.commands.uiautomator.Launcher.Command;
import com.android.uiautomator.testrunner.UiAutomatorTestRunner;
import dalvik.system.DexFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Implementation of the runtest sub command
*
*/
public class RunTestCommand extends Command {
private static final String LOGTAG = RunTestCommand.class.getSimpleName();
private static final String OUTPUT_SIMPLE = "simple";
private static final String OUTPUT_FORMAT_KEY = "outputFormat";
private static final String CLASS_PARAM = "class";
private static final String JARS_PARAM = "jars";
private static final String DEBUG_PARAM = "debug";
private static final String RUNNER_PARAM = "runner";
private static final String CLASS_SEPARATOR = ",";
private static final String JARS_SEPARATOR = ":";
private static final int ARG_OK = 0;
private static final int ARG_FAIL_INCOMPLETE_E = -1;
private static final int ARG_FAIL_INCOMPLETE_C = -2;
private static final int ARG_FAIL_NO_CLASS = -3;
private static final int ARG_FAIL_RUNNER = -4;
private static final int ARG_FAIL_UNSUPPORTED = -99;
private final Bundle mParams = new Bundle();
private final List<String> mTestClasses = new ArrayList<String>();
private boolean mDebug;
private boolean mMonkey = false;
private String mRunnerClassName;
private UiAutomatorTestRunner mRunner;
public RunTestCommand() {
super("runtest");
}
@Override
public void run(String[] args) {
int ret = parseArgs(args);
switch (ret) {
case ARG_FAIL_INCOMPLETE_C:
System.err.println("Incomplete '-c' parameter.");
System.exit(ARG_FAIL_INCOMPLETE_C);
break;
case ARG_FAIL_INCOMPLETE_E:
System.err.println("Incomplete '-e' parameter.");
System.exit(ARG_FAIL_INCOMPLETE_E);
break;
case ARG_FAIL_UNSUPPORTED:
System.err.println("Unsupported standalone parameter.");
System.exit(ARG_FAIL_UNSUPPORTED);
break;
default:
break;
}
if (mTestClasses.isEmpty()) {
addTestClassesFromJars();
if (mTestClasses.isEmpty()) {
System.err.println("No test classes found.");
System.exit(ARG_FAIL_NO_CLASS);
}
}
getRunner().run(mTestClasses, mParams, mDebug, mMonkey);
}
private int parseArgs(String[] args) {
// we are parsing for these parameters:
// -e <key> <value>
// key-value pairs
// special ones are:
// key is "class", parameter is passed onto JUnit as class name to run
// key is "debug", parameter will determine whether to wait for debugger
// to attach
// -c <class name>
// -s turns on the simple output format
// equivalent to -e class <class name>, i.e. passed onto JUnit
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-e")) {
if (i + 2 < args.length) {
String key = args[++i];
String value = args[++i];
if (CLASS_PARAM.equals(key)) {
addTestClasses(value);
} else if (DEBUG_PARAM.equals(key)) {
mDebug = "true".equals(value) || "1".equals(value);
} else if (RUNNER_PARAM.equals(key)) {
mRunnerClassName = value;
} else {
mParams.putString(key, value);
}
} else {
return ARG_FAIL_INCOMPLETE_E;
}
} else if (args[i].equals("-c")) {
if (i + 1 < args.length) {
addTestClasses(args[++i]);
} else {
return ARG_FAIL_INCOMPLETE_C;
}
} else if (args[i].equals("--monkey")) {
mMonkey = true;
} else if (args[i].equals("-s")) {
mParams.putString(OUTPUT_FORMAT_KEY, OUTPUT_SIMPLE);
} else {
return ARG_FAIL_UNSUPPORTED;
}
}
return ARG_OK;
}
protected UiAutomatorTestRunner getRunner() {
if (mRunner != null) {
return mRunner;
}
if (mRunnerClassName == null) {
mRunner = new UiAutomatorTestRunner();
return mRunner;
}
// use reflection to get the runner
Object o = null;
try {
Class<?> clazz = Class.forName(mRunnerClassName);
o = clazz.newInstance();
} catch (ClassNotFoundException cnfe) {
System.err.println("Cannot find runner: " + mRunnerClassName);
System.exit(ARG_FAIL_RUNNER);
} catch (InstantiationException ie) {
System.err.println("Cannot instantiate runner: " + mRunnerClassName);
System.exit(ARG_FAIL_RUNNER);
} catch (IllegalAccessException iae) {
System.err.println("Constructor of runner " + mRunnerClassName + " is not accessibile");
System.exit(ARG_FAIL_RUNNER);
}
try {
UiAutomatorTestRunner runner = (UiAutomatorTestRunner)o;
mRunner = runner;
return runner;
} catch (ClassCastException cce) {
System.err.println("Specified runner is not subclass of "
+ UiAutomatorTestRunner.class.getSimpleName());
System.exit(ARG_FAIL_RUNNER);
}
// won't reach here
return null;
}
/**
* Add test classes from a potentially comma separated list
* @param classes
*/
private void addTestClasses(String classes) {
String[] classArray = classes.split(CLASS_SEPARATOR);
for (String clazz : classArray) {
mTestClasses.add(clazz);
}
}
/**
* Add test classes from jars passed on the command line. Use this if nothing was explicitly
* specified on the command line.
*/
private void addTestClassesFromJars() {
String jars = mParams.getString(JARS_PARAM);
if (jars == null) return;
String[] jarFileNames = jars.split(JARS_SEPARATOR);
for (String fileName : jarFileNames) {
fileName = fileName.trim();
if (fileName.isEmpty()) continue;
try {
DexFile dexFile = new DexFile(fileName);
for(Enumeration<String> e = dexFile.entries(); e.hasMoreElements();) {
String className = e.nextElement();
if (isTestClass(className)) {
mTestClasses.add(className);
}
}
dexFile.close();
} catch (IOException e) {
Log.w(LOGTAG, String.format("Could not read %s: %s", fileName, e.getMessage()));
}
}
}
/**
* Tries to determine if a given class is a test class. A test class has to inherit from
* UiAutomator test case and it must be a top-level class.
* @param className
* @return
*/
private boolean isTestClass(String className) {
try {
Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
if (clazz.getEnclosingClass() != null) return false;
return getRunner().getTestCaseFilter().accept(clazz);
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public String detailedOptions() {
return " runtest <class spec> [options]\n"
+ " <class spec>: <JARS> < -c <CLASSES> | -e class <CLASSES> >\n"
+ " <JARS>: a list of jar files containing test classes and dependencies. If\n"
+ " the path is relative, it's assumed to be under /data/local/tmp. Use\n"
+ " absolute path if the file is elsewhere. Multiple files can be\n"
+ " specified, separated by space.\n"
+ " <CLASSES>: a list of test class names to run, separated by comma. To\n"
+ " a single method, use TestClass#testMethod format. The -e or -c option\n"
+ " may be repeated. This option is not required and if not provided then\n"
+ " all the tests in provided jars will be run automatically.\n"
+ " options:\n"
+ " --nohup: trap SIG_HUP, so test won't terminate even if parent process\n"
+ " is terminated, e.g. USB is disconnected.\n"
+ " -e debug [true|false]: wait for debugger to connect before starting.\n"
+ " -e runner [CLASS]: use specified test runner class instead. If\n"
+ " unspecified, framework default runner will be used.\n"
+ " -e <NAME> <VALUE>: other name-value pairs to be passed to test classes.\n"
+ " May be repeated.\n"
+ " -e outputFormat simple | -s: enabled less verbose JUnit style output.\n";
}
@Override
public String shortHelp() {
return "executes UI automation tests";
}
}

View File

@ -0,0 +1,123 @@
#
# 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.
#
# Script to start "uiautomator" on the device
#
# The script does a couple of things:
# * Use an alternative dalvik cache when running as non-root. Jar file needs
# to be dexopt'd to run in Dalvik. For plain jar files, this is done at first
# use. shell user does not have write permission to default system Dalvik
# cache so we redirect to an alternative cache
# * special processing for subcommand 'runtest':
# * '--nohup' allows process continue to run even if parent process that
# started it has already terminated. We parse for this parameter and set
# signal trap. This is useful for testing with USB disconnected
# * all jar files that the test classes resides in, or dependent on are
# provided on command line and exported to CLASSPATH environment variable
# before starting the Java code. This offloads the task of class loading
# and resolving of cross jar class dependency to Dalvik
# * all other subcommand or options are directly passed into Java code for
# further parsing
export run_base=/data/local/tmp
export base=/system
# if not running as root, trick dalvik into using an alternative dex cache
if [ ${USER_ID} -ne 0 ]; then
tmp_cache=${run_base}/dalvik-cache
if [ ! -d ${tmp_cache} ]; then
mkdir -p ${tmp_cache}
fi
export ANDROID_DATA=${run_base}
fi
# take first parameter as the command
cmd=${1}
if [ -z "${1}" ]; then
cmd="help"
fi
# strip the command parameter
if [ -n "${1}" ]; then
shift
fi
CLASSPATH=/system/framework/android.test.runner.jar:${base}/framework/uiautomator.jar
# eventually args will be what get passed down to Java code
args=
# we also pass the list of jar files, so we can extract class names for tests
# if they are not explicitly specified
jars=
# special case pre-processing for 'runtest' command
if [ "${cmd}" == "runtest" ]; then
# first parse the jar paths
while [ true ]; do
if [ -z "${1}" ] && [ -z "${jars}" ]; then
echo "Error: more parameters expected for runtest; please see usage for details"
cmd="help"
break
fi
if [ -z "${1}" ]; then
break
fi
jar=${1}
if [ "${1:0:1}" = "-" ]; then
# we are done with jars, starting with parameters now
break
fi
# if relative path, append the default path prefix
if [ "${1:0:1}" != "/" ]; then
jar=${run_base}/${1}
fi
# about to add the file to class path, check if it's valid
if [ ! -f ${jar} ]; then
echo "Error: ${jar} does not exist"
# force to print help message
cmd="help"
break
fi
jars=${jars}:${jar}
# done processing current arg, moving on
shift
done
# look for --nohup: if found, consume it and trap SIG_HUP, otherwise just
# append the arg to args
while [ -n "${1}" ]; do
if [ "${1}" = "--nohup" ]; then
trap "" HUP
shift
else
args="${args} ${1}"
shift
fi
done
else
# if cmd is not 'runtest', just take the rest of the args
args=${@}
fi
args="${cmd} ${args}"
if [ -n "${jars}" ]; then
args="${args} -e jars ${jars}"
fi
CLASSPATH=${CLASSPATH}:${jars}
export CLASSPATH
exec app_process ${base}/bin com.android.commands.uiautomator.Launcher ${args}

View File

@ -0,0 +1,29 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
$(call all-java-files-under, ../library/core-src)
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_MODULE := uiautomator-instrumentation
# TODO: change this to 18 when it's available
LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)

View File

@ -0,0 +1,60 @@
/*
* 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 com.android.uiautomator.core;
import android.app.Service;
import android.app.UiAutomation;
import android.content.Context;
import android.os.PowerManager;
import android.view.Display;
import android.view.ViewConfiguration;
import android.view.WindowManager;
/**
* @hide
*/
public class InstrumentationUiAutomatorBridge extends UiAutomatorBridge {
private final Context mContext;
public InstrumentationUiAutomatorBridge(Context context, UiAutomation uiAutomation) {
super(uiAutomation);
mContext = context;
}
public Display getDefaultDisplay() {
WindowManager windowManager = (WindowManager)
mContext.getSystemService(Service.WINDOW_SERVICE);
return windowManager.getDefaultDisplay();
}
@Override
public int getRotation() {
return getDefaultDisplay().getRotation();
}
@Override
public boolean isScreenOn() {
PowerManager pm = (PowerManager)
mContext.getSystemService(Service.POWER_SERVICE);
return pm.isScreenOn();
}
public long getSystemLongPressTime() {
return ViewConfiguration.getLongPressTimeout();
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.os.Bundle;
/**
* Provides auxiliary support for running test cases
*
* @since API Level 16
*/
public interface IAutomationSupport {
/**
* Allows the running test cases to send out interim status
*
* @param resultCode
* @param status status report, consisting of key value pairs
* @since API Level 16
*/
public void sendStatus(int resultCode, Bundle status);
}

View File

@ -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 com.android.uiautomator.testrunner;
import android.app.Instrumentation;
import android.os.Bundle;
/**
* A wrapper around {@link Instrumentation} to provide sendStatus function
*
* Provided for backwards compatibility purpose. New code should use
* {@link Instrumentation#sendStatus(int, Bundle)} instead.
*
*/
class InstrumentationAutomationSupport implements IAutomationSupport {
private Instrumentation mInstrumentation;
InstrumentationAutomationSupport(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
}
@Override
public void sendStatus(int resultCode, Bundle status) {
mInstrumentation.sendStatus(resultCode, status);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.test.AndroidTestRunner;
import android.test.InstrumentationTestRunner;
import com.android.uiautomator.core.Tracer;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestListener;
/**
* Test runner for {@link UiAutomatorTestCase}s. Such tests are executed
* on the device and have access to an applications context.
*/
public class UiAutomatorInstrumentationTestRunner extends InstrumentationTestRunner {
@Override
public void onStart() {
// process runner arguments before test starts
String traceType = getArguments().getString("traceOutputMode");
if(traceType != null) {
Tracer.Mode mode = Tracer.Mode.valueOf(Tracer.Mode.class, traceType);
if (mode == Tracer.Mode.FILE || mode == Tracer.Mode.ALL) {
String filename = getArguments().getString("traceLogFilename");
if (filename == null) {
throw new RuntimeException("Name of log file not specified. " +
"Please specify it using traceLogFilename parameter");
}
Tracer.getInstance().setOutputFilename(filename);
}
Tracer.getInstance().setOutputMode(mode);
}
super.onStart();
}
@Override
protected AndroidTestRunner getAndroidTestRunner() {
AndroidTestRunner testRunner = super.getAndroidTestRunner();
testRunner.addTestListener(new TestListener() {
@Override
public void startTest(Test test) {
if (test instanceof UiAutomatorTestCase) {
((UiAutomatorTestCase)test).initialize(getArguments());
}
}
@Override
public void endTest(Test test) {
}
@Override
public void addFailure(Test test, AssertionFailedError e) {
}
@Override
public void addError(Test test, Throwable t) {
}
});
return testRunner;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.app.Instrumentation;
import android.os.Bundle;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
import com.android.uiautomator.core.UiDevice;
/**
* UI Automator test case that is executed on the device.
*/
public class UiAutomatorTestCase extends InstrumentationTestCase {
private Bundle mParams;
private IAutomationSupport mAutomationSupport;
/**
* Get current instance of {@link UiDevice}. Works similar to calling the static
* {@link UiDevice#getInstance()} from anywhere in the test classes.
* @since API Level 16
*/
public UiDevice getUiDevice() {
return UiDevice.getInstance();
}
/**
* Get command line parameters. On the command line when passing <code>-e key value</code>
* pairs, the {@link Bundle} will have the key value pairs conveniently available to the
* tests.
* @since API Level 16
*/
public Bundle getParams() {
return mParams;
}
void setAutomationSupport(IAutomationSupport automationSupport) {
mAutomationSupport = automationSupport;
}
/**
* Provides support for running tests to report interim status
*
* @return IAutomationSupport
* @since API Level 16
* @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
*/
public IAutomationSupport getAutomationSupport() {
if (mAutomationSupport == null) {
mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
}
return mAutomationSupport;
}
/**
* Initializes this test case.
*
* @param params Instrumentation arguments.
*/
void initialize(Bundle params) {
mParams = params;
// check if this is a monkey test mode
String monkeyVal = mParams.getString("monkey");
if (monkeyVal != null) {
// only if the monkey key is specified, we alter the state of monkey
// else we should leave things as they are.
getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
}
UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
getInstrumentation().getContext(),
getInstrumentation().getUiAutomation()));
}
/**
* Calls {@link SystemClock#sleep(long)} to sleep
* @param ms is in milliseconds.
* @since API Level 16
*/
public void sleep(long ms) {
SystemClock.sleep(ms);
}
}

View File

@ -0,0 +1,132 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
uiautomator.core_src_files := $(call all-java-files-under, core-src) \
$(call all-java-files-under, testrunner-src)
uiautomator.core_java_libraries := android.test.runner core-junit
uiautomator_internal_api_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_api.txt
uiautomator_internal_removed_api_file := \
$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_removed_api.txt
###############################################
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(uiautomator.core_src_files)
LOCAL_MODULE := uiautomator.core
LOCAL_JAVA_LIBRARIES := android.test.runner
include $(BUILD_STATIC_JAVA_LIBRARY)
###############################################
# Generate the stub source files
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(uiautomator.core_src_files)
LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \
$(LOCAL_PATH)/testrunner-src
LOCAL_DROIDDOC_HTML_DIR :=
LOCAL_DROIDDOC_OPTIONS:= \
-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_uiautomator_intermediates/src \
-stubpackages com.android.uiautomator.core:com.android.uiautomator.testrunner \
-api $(uiautomator_internal_api_file) \
-removedApi $(uiautomator_internal_removed_api_file)
LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_MODULE := uiautomator-stubs
include $(BUILD_DROIDDOC)
uiautomator_stubs_stamp := $(full_target)
$(uiautomator_internal_api_file) : $(full_target)
###############################################
# Build the stub source files into a jar.
include $(CLEAR_VARS)
LOCAL_MODULE := android_uiautomator
LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
LOCAL_SOURCE_FILES_ALL_GENERATED := true
include $(BUILD_STATIC_JAVA_LIBRARY)
# Make sure to run droiddoc first to generate the stub source files.
$(full_classes_compiled_jar) : $(uiautomator_stubs_stamp)
uiautomator_stubs_jar := $(full_classes_compiled_jar)
###############################################
# API check
# Please refer to build/core/tasks/apicheck.mk.
uiautomator_api_dir := frameworks/base/cmds/uiautomator/api
last_released_sdk_version := $(lastword $(call numerically_sort, \
$(filter-out current, \
$(patsubst $(uiautomator_api_dir)/%.txt,%, $(wildcard $(uiautomator_api_dir)/*.txt)) \
)))
checkapi_last_error_level_flags := \
-hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 \
-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
-error 16 -error 17 -error 18
# Check that the API we're building hasn't broken the last-released SDK version.
$(eval $(call check-api, \
uiautomator-checkapi-last, \
$(uiautomator_api_dir)/$(last_released_sdk_version).txt, \
$(uiautomator_internal_api_file), \
$(uiautomator_api_dir)/removed.txt, \
$(uiautomator_internal_removed_api_file), \
$(checkapi_last_error_level_flags), \
cat $(LOCAL_PATH)/apicheck_msg_last.txt, \
$(uiautomator_stubs_jar), \
$(uiautomator_stubs_stamp)))
checkapi_current_error_level_flags := \
-error 2 -error 3 -error 4 -error 5 -error 6 \
-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
-error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
-error 25
# Check that the API we're building hasn't changed from the not-yet-released
# SDK version.
$(eval $(call check-api, \
uiautomator-checkapi-current, \
$(uiautomator_api_dir)/current.txt, \
$(uiautomator_internal_api_file), \
$(uiautomator_api_dir)/removed.txt, \
$(uiautomator_internal_removed_api_file), \
$(checkapi_current_error_level_flags), \
cat $(LOCAL_PATH)/apicheck_msg_current.txt, \
$(uiautomator_stubs_jar), \
$(uiautomator_stubs_stamp)))
.PHONY: update-uiautomator-api
update-uiautomator-api: PRIVATE_API_DIR := $(uiautomator_api_dir)
update-uiautomator-api: PRIVATE_REMOVED_API_FILE := $(uiautomator_internal_removed_api_file)
update-uiautomator-api: $(uiautomator_internal_api_file) | $(ACP)
@echo Copying uiautomator current.txt
$(hide) $(ACP) $< $(PRIVATE_API_DIR)/current.txt
@echo Copying uiautomator removed.txt
$(hide) $(ACP) $(PRIVATE_REMOVED_API_FILE) $(PRIVATE_API_DIR)/removed.txt
###############################################
# clean up temp vars
uiautomator.core_src_files :=
uiautomator.core_java_libraries :=
uiautomator_stubs_stamp :=
uiautomator_internal_api_file :=
uiautomator_stubs_jar :=
uiautomator_api_dir :=
checkapi_last_error_level_flags :=
checkapi_current_error_level_flags :=

View File

@ -0,0 +1,17 @@
******************************
You have tried to change the API from what has been previously approved.
To make these errors go away, you have two choices:
1) You can add "@hide" javadoc comments to the methods, etc. listed in the
errors above.
2) You can update current.txt by executing the following command:
make update-uiautomator-api
To submit the revised current.txt to the main Android repository,
you will need approval.
******************************

View File

@ -0,0 +1,7 @@
******************************
You have tried to change the API from what has been previously released in
an SDK. Please fix the errors listed above.
******************************

View File

@ -0,0 +1,250 @@
/*
* 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 com.android.uiautomator.core;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.util.Xml;
import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
/**
*
* @hide
*/
public class AccessibilityNodeInfoDumper {
private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
private static final String[] NAF_EXCLUDED_CLASSES = new String[] {
android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
};
/**
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
* and generates an xml dump into the /data/local/window_dump.xml
* @param root The root accessibility node.
* @param rotation The rotaion of current display
* @param width The pixel width of current display
* @param height The pixel height of current display
*/
public static void dumpWindowToFile(AccessibilityNodeInfo root, int rotation,
int width, int height) {
File baseDir = new File(Environment.getDataDirectory(), "local");
if (!baseDir.exists()) {
baseDir.mkdir();
baseDir.setExecutable(true, false);
baseDir.setWritable(true, false);
baseDir.setReadable(true, false);
}
dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
rotation, width, height);
}
/**
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
* and generates an xml dump to the location specified by <code>dumpFile</code>
* @param root The root accessibility node.
* @param dumpFile The file to dump to.
* @param rotation The rotaion of current display
* @param width The pixel width of current display
* @param height The pixel height of current display
*/
public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
int width, int height) {
if (root == null) {
return;
}
final long startTime = SystemClock.uptimeMillis();
try {
FileWriter writer = new FileWriter(dumpFile);
XmlSerializer serializer = Xml.newSerializer();
StringWriter stringWriter = new StringWriter();
serializer.setOutput(stringWriter);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "hierarchy");
serializer.attribute("", "rotation", Integer.toString(rotation));
dumpNodeRec(root, serializer, 0, width, height);
serializer.endTag("", "hierarchy");
serializer.endDocument();
writer.write(stringWriter.toString());
writer.close();
} catch (IOException e) {
Log.e(LOGTAG, "failed to dump window to file", e);
}
final long endTime = SystemClock.uptimeMillis();
Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
}
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index,
int width, int height) throws IOException {
serializer.startTag("", "node");
if (!nafExcludedClass(node) && !nafCheck(node))
serializer.attribute("", "NAF", Boolean.toString(true));
serializer.attribute("", "index", Integer.toString(index));
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
node, width, height).toShortString());
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
dumpNodeRec(child, serializer, i, width, height);
child.recycle();
} else {
Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
i, count, node.toString()));
}
}
serializer.endTag("", "node");
}
/**
* The list of classes to exclude my not be complete. We're attempting to
* only reduce noise from standard layout classes that may be falsely
* configured to accept clicks and are also enabled.
*
* @param node
* @return true if node is excluded.
*/
private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
String className = safeCharSeqToString(node.getClassName());
for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
if(className.endsWith(excludedClassName))
return true;
}
return false;
}
/**
* We're looking for UI controls that are enabled, clickable but have no
* text nor content-description. Such controls configuration indicate an
* interactive control is present in the UI and is most likely not
* accessibility friendly. We refer to such controls here as NAF controls
* (Not Accessibility Friendly)
*
* @param node
* @return false if a node fails the check, true if all is OK
*/
private static boolean nafCheck(AccessibilityNodeInfo node) {
boolean isNaf = node.isClickable() && node.isEnabled()
&& safeCharSeqToString(node.getContentDescription()).isEmpty()
&& safeCharSeqToString(node.getText()).isEmpty();
if (!isNaf)
return true;
// check children since sometimes the containing element is clickable
// and NAF but a child's text or description is available. Will assume
// such layout as fine.
return childNafCheck(node);
}
/**
* This should be used when it's already determined that the node is NAF and
* a further check of its children is in order. A node maybe a container
* such as LinerLayout and may be set to be clickable but have no text or
* content description but it is counting on one of its children to fulfill
* the requirement for being accessibility friendly by having one or more of
* its children fill the text or content-description. Such a combination is
* considered by this dumper as acceptable for accessibility.
*
* @param node
* @return false if node fails the check.
*/
private static boolean childNafCheck(AccessibilityNodeInfo node) {
int childCount = node.getChildCount();
for (int x = 0; x < childCount; x++) {
AccessibilityNodeInfo childNode = node.getChild(x);
if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
|| !safeCharSeqToString(childNode.getText()).isEmpty())
return true;
if (childNafCheck(childNode))
return true;
}
return false;
}
private static String safeCharSeqToString(CharSequence cs) {
if (cs == null)
return "";
else {
return stripInvalidXMLChars(cs);
}
}
private static String stripInvalidXMLChars(CharSequence cs) {
StringBuffer ret = new StringBuffer();
char ch;
/* http://www.w3.org/TR/xml11/#charsets
[#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
[#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
[#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
[#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
[#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
[#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
[#x10FFFE-#x10FFFF].
*/
for (int i = 0; i < cs.length(); i++) {
ch = cs.charAt(i);
if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
(ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
(ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
(ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
(ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
(ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
(ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
(ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
(ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
(ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
(ch >= 0x10FFFE && ch <= 0x10FFFF))
ret.append(".");
else
ret.append(ch);
}
return ret.toString();
}
}

View File

@ -0,0 +1,52 @@
/*
* 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 com.android.uiautomator.core;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* This class contains static helper methods to work with
* {@link AccessibilityNodeInfo}
*/
class AccessibilityNodeInfoHelper {
/**
* Returns the node's bounds clipped to the size of the display
*
* @param node
* @param width pixel width of the display
* @param height pixel height of the display
* @return null if node is null, else a Rect containing visible bounds
*/
static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
if (node == null) {
return null;
}
// targeted node's bounds
Rect nodeRect = new Rect();
node.getBoundsInScreen(nodeRect);
Rect displayRect = new Rect();
displayRect.top = 0;
displayRect.left = 0;
displayRect.right = width;
displayRect.bottom = height;
nodeRect.intersect(displayRect);
return nodeRect;
}
}

View File

@ -0,0 +1,224 @@
/*
* 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 com.android.uiautomator.core;
/**
* Allows you to set key parameters for running uiautomator tests. The new
* settings take effect immediately and can be changed any time during a test run.
*
* To modify parameters using Configurator, first obtain an instance by calling
* {@link #getInstance()}. As a best practice, make sure you always save
* the original value of any parameter that you are modifying. After running your
* tests with the modified parameters, make sure to also restore
* the original parameter values, otherwise this will impact other tests cases.
* @since API Level 18
*/
public final class Configurator {
private long mWaitForIdleTimeout = 10 * 1000;
private long mWaitForSelector = 10 * 1000;
private long mWaitForActionAcknowledgment = 3 * 1000;
// The events for a scroll typically complete even before touchUp occurs.
// This short timeout to make sure we get the very last in cases where the above isn't true.
private long mScrollEventWaitTimeout = 200; // ms
// Default is inject as fast as we can
private long mKeyInjectionDelay = 0; // ms
// reference to self
private static Configurator sConfigurator;
private Configurator() {
/* hide constructor */
}
/**
* Retrieves a singleton instance of Configurator.
*
* @return Configurator instance
* @since API Level 18
*/
public static Configurator getInstance() {
if (sConfigurator == null) {
sConfigurator = new Configurator();
}
return sConfigurator;
}
/**
* Sets the timeout for waiting for the user interface to go into an idle
* state before starting a uiautomator action.
*
* By default, all core uiautomator objects except {@link UiDevice} will perform
* this wait before starting to search for the widget specified by the
* object's {@link UiSelector}. Once the idle state is detected or the
* timeout elapses (whichever occurs first), the object will start to wait
* for the selector to find a match.
* See {@link #setWaitForSelectorTimeout(long)}
*
* @param timeout Timeout value in milliseconds
* @return self
* @since API Level 18
*/
public Configurator setWaitForIdleTimeout(long timeout) {
mWaitForIdleTimeout = timeout;
return this;
}
/**
* Gets the current timeout used for waiting for the user interface to go
* into an idle state.
*
* By default, all core uiautomator objects except {@link UiDevice} will perform
* this wait before starting to search for the widget specified by the
* object's {@link UiSelector}. Once the idle state is detected or the
* timeout elapses (whichever occurs first), the object will start to wait
* for the selector to find a match.
* See {@link #setWaitForSelectorTimeout(long)}
*
* @return Current timeout value in milliseconds
* @since API Level 18
*/
public long getWaitForIdleTimeout() {
return mWaitForIdleTimeout;
}
/**
* Sets the timeout for waiting for a widget to become visible in the user
* interface so that it can be matched by a selector.
*
* Because user interface content is dynamic, sometimes a widget may not
* be visible immediately and won't be detected by a selector. This timeout
* allows the uiautomator framework to wait for a match to be found, up until
* the timeout elapses.
*
* @param timeout Timeout value in milliseconds.
* @return self
* @since API Level 18
*/
public Configurator setWaitForSelectorTimeout(long timeout) {
mWaitForSelector = timeout;
return this;
}
/**
* Gets the current timeout for waiting for a widget to become visible in
* the user interface so that it can be matched by a selector.
*
* Because user interface content is dynamic, sometimes a widget may not
* be visible immediately and won't be detected by a selector. This timeout
* allows the uiautomator framework to wait for a match to be found, up until
* the timeout elapses.
*
* @return Current timeout value in milliseconds
* @since API Level 18
*/
public long getWaitForSelectorTimeout() {
return mWaitForSelector;
}
/**
* Sets the timeout for waiting for an acknowledgement of an
* uiautomtor scroll swipe action.
*
* The acknowledgment is an <a href="http://developer.android.com/reference/android/view/accessibility/AccessibilityEvent.html">AccessibilityEvent</a>,
* corresponding to the scroll action, that lets the framework determine if
* the scroll action was successful. Generally, this timeout should not be modified.
* See {@link UiScrollable}
*
* @param timeout Timeout value in milliseconds
* @return self
* @since API Level 18
*/
public Configurator setScrollAcknowledgmentTimeout(long timeout) {
mScrollEventWaitTimeout = timeout;
return this;
}
/**
* Gets the timeout for waiting for an acknowledgement of an
* uiautomtor scroll swipe action.
*
* The acknowledgment is an <a href="http://developer.android.com/reference/android/view/accessibility/AccessibilityEvent.html">AccessibilityEvent</a>,
* corresponding to the scroll action, that lets the framework determine if
* the scroll action was successful. Generally, this timeout should not be modified.
* See {@link UiScrollable}
*
* @return current timeout in milliseconds
* @since API Level 18
*/
public long getScrollAcknowledgmentTimeout() {
return mScrollEventWaitTimeout;
}
/**
* Sets the timeout for waiting for an acknowledgment of generic uiautomator
* actions, such as clicks, text setting, and menu presses.
*
* The acknowledgment is an <a href="http://developer.android.com/reference/android/view/accessibility/AccessibilityEvent.html">AccessibilityEvent</a>,
* corresponding to an action, that lets the framework determine if the
* action was successful. Generally, this timeout should not be modified.
* See {@link UiObject}
*
* @param timeout Timeout value in milliseconds
* @return self
* @since API Level 18
*/
public Configurator setActionAcknowledgmentTimeout(long timeout) {
mWaitForActionAcknowledgment = timeout;
return this;
}
/**
* Gets the current timeout for waiting for an acknowledgment of generic
* uiautomator actions, such as clicks, text setting, and menu presses.
*
* The acknowledgment is an <a href="http://developer.android.com/reference/android/view/accessibility/AccessibilityEvent.html">AccessibilityEvent</a>,
* corresponding to an action, that lets the framework determine if the
* action was successful. Generally, this timeout should not be modified.
* See {@link UiObject}
*
* @return current timeout in milliseconds
* @since API Level 18
*/
public long getActionAcknowledgmentTimeout() {
return mWaitForActionAcknowledgment;
}
/**
* Sets a delay between key presses when injecting text input.
* See {@link UiObject#setText(String)}
*
* @param delay Delay value in milliseconds
* @return self
* @since API Level 18
*/
public Configurator setKeyInjectionDelay(long delay) {
mKeyInjectionDelay = delay;
return this;
}
/**
* Gets the current delay between key presses when injecting text input.
* See {@link UiObject#setText(String)}
*
* @return current delay in milliseconds
* @since API Level 18
*/
public long getKeyInjectionDelay() {
return mKeyInjectionDelay;
}
}

View File

@ -0,0 +1,795 @@
/*
* 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 com.android.uiautomator.core;
import android.accessibilityservice.AccessibilityService;
import android.app.UiAutomation;
import android.app.UiAutomation.AccessibilityEventFilter;
import android.graphics.Point;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* The InteractionProvider is responsible for injecting user events such as touch events
* (includes swipes) and text key events into the system. To do so, all it needs to know about
* are coordinates of the touch events and text for the text input events.
* The InteractionController performs no synchronization. It will fire touch and text input events
* as fast as it receives them. All idle synchronization is performed prior to querying the
* hierarchy. See {@link QueryController}
*/
class InteractionController {
private static final String LOG_TAG = InteractionController.class.getSimpleName();
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private final KeyCharacterMap mKeyCharacterMap =
KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
private final UiAutomatorBridge mUiAutomatorBridge;
private static final long REGULAR_CLICK_LENGTH = 100;
private long mDownTime;
// Inserted after each motion event injection.
private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
public InteractionController(UiAutomatorBridge bridge) {
mUiAutomatorBridge = bridge;
}
/**
* Predicate for waiting for any of the events specified in the mask
*/
class WaitForAnyEventPredicate implements AccessibilityEventFilter {
int mMask;
WaitForAnyEventPredicate(int mask) {
mMask = mask;
}
@Override
public boolean accept(AccessibilityEvent t) {
// check current event in the list
if ((t.getEventType() & mMask) != 0) {
return true;
}
// no match yet
return false;
}
}
/**
* Predicate for waiting for all the events specified in the mask and populating
* a ctor passed list with matching events. User of this Predicate must recycle
* all populated events in the events list.
*/
class EventCollectingPredicate implements AccessibilityEventFilter {
int mMask;
List<AccessibilityEvent> mEventsList;
EventCollectingPredicate(int mask, List<AccessibilityEvent> events) {
mMask = mask;
mEventsList = events;
}
@Override
public boolean accept(AccessibilityEvent t) {
// check current event in the list
if ((t.getEventType() & mMask) != 0) {
// For the events you need, always store a copy when returning false from
// predicates since the original will automatically be recycled after the call.
mEventsList.add(AccessibilityEvent.obtain(t));
}
// get more
return false;
}
}
/**
* Predicate for waiting for every event specified in the mask to be matched at least once
*/
class WaitForAllEventPredicate implements AccessibilityEventFilter {
int mMask;
WaitForAllEventPredicate(int mask) {
mMask = mask;
}
@Override
public boolean accept(AccessibilityEvent t) {
// check current event in the list
if ((t.getEventType() & mMask) != 0) {
// remove from mask since this condition is satisfied
mMask &= ~t.getEventType();
// Since we're waiting for all events to be matched at least once
if (mMask != 0)
return false;
// all matched
return true;
}
// no match yet
return false;
}
}
/**
* Helper used by methods to perform actions and wait for any accessibility events and return
* predicated on predefined filter.
*
* @param command
* @param filter
* @param timeout
* @return
*/
private AccessibilityEvent runAndWaitForEvents(Runnable command,
AccessibilityEventFilter filter, long timeout) {
try {
return mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, filter,
timeout);
} catch (TimeoutException e) {
Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events");
return null;
} catch (Exception e) {
Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
return null;
}
}
/**
* Send keys and blocks until the first specified accessibility event.
*
* Most key presses will cause some UI change to occur. If the device is busy, this will
* block until the device begins to process the key press at which point the call returns
* and normal wait for idle processing may begin. If no events are detected for the
* timeout period specified, the call will return anyway with false.
*
* @param keyCode
* @param metaState
* @param eventType
* @param timeout
* @return true if events is received, otherwise false.
*/
public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
final int eventType, long timeout) {
Runnable command = new Runnable() {
@Override
public void run() {
final long eventTime = SystemClock.uptimeMillis();
KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
if (injectEventSync(downEvent)) {
KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
injectEventSync(upEvent);
}
}
};
return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
!= null;
}
/**
* Clicks at coordinates without waiting for device idle. This may be used for operations
* that require stressing the target.
* @param x
* @param y
* @return true if the click executed successfully
*/
public boolean clickNoSync(int x, int y) {
Log.d(LOG_TAG, "clickNoSync (" + x + ", " + y + ")");
if (touchDown(x, y)) {
SystemClock.sleep(REGULAR_CLICK_LENGTH);
if (touchUp(x, y))
return true;
}
return false;
}
/**
* Click at coordinates and blocks until either accessibility event TYPE_WINDOW_CONTENT_CHANGED
* or TYPE_VIEW_SELECTED are received.
*
* @param x
* @param y
* @param timeout waiting for event
* @return true if events are received, else false if timeout.
*/
public boolean clickAndSync(final int x, final int y, long timeout) {
String logString = String.format("clickAndSync(%d, %d)", x, y);
Log.d(LOG_TAG, logString);
return runAndWaitForEvents(clickRunnable(x, y), new WaitForAnyEventPredicate(
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
AccessibilityEvent.TYPE_VIEW_SELECTED), timeout) != null;
}
/**
* Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed
* by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED,
* no further waits will be performed and the function returns.
* @param x
* @param y
* @param timeout waiting for event
* @return true if both events occurred in the expected order
*/
public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) {
String logString = String.format("clickAndWaitForNewWindow(%d, %d)", x, y);
Log.d(LOG_TAG, logString);
return runAndWaitForEvents(clickRunnable(x, y), new WaitForAllEventPredicate(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), timeout) != null;
}
/**
* Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, Predicate, long) to
* perform a click.
*
* @param x coordinate
* @param y coordinate
* @return Runnable
*/
private Runnable clickRunnable(final int x, final int y) {
return new Runnable() {
@Override
public void run() {
if(touchDown(x, y)) {
SystemClock.sleep(REGULAR_CLICK_LENGTH);
touchUp(x, y);
}
}
};
}
/**
* Touches down for a long press at the specified coordinates.
*
* @param x
* @param y
* @return true if successful.
*/
public boolean longTapNoSync(int x, int y) {
if (DEBUG) {
Log.d(LOG_TAG, "longTapNoSync (" + x + ", " + y + ")");
}
if (touchDown(x, y)) {
SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
if(touchUp(x, y)) {
return true;
}
}
return false;
}
private boolean touchDown(int x, int y) {
if (DEBUG) {
Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")");
}
mDownTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(
mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y, 1);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
return injectEventSync(event);
}
private boolean touchUp(int x, int y) {
if (DEBUG) {
Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")");
}
final long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(
mDownTime, eventTime, MotionEvent.ACTION_UP, x, y, 1);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mDownTime = 0;
return injectEventSync(event);
}
private boolean touchMove(int x, int y) {
if (DEBUG) {
Log.d(LOG_TAG, "touchMove (" + x + ", " + y + ")");
}
final long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(
mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
return injectEventSync(event);
}
/**
* Handle swipes in any direction where the result is a scroll event. This call blocks
* until the UI has fired a scroll event or timeout.
* @param downX
* @param downY
* @param upX
* @param upY
* @param steps
* @return true if we are not at the beginning or end of the scrollable view.
*/
public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
final int steps) {
Log.d(LOG_TAG, "scrollSwipe (" + downX + ", " + downY + ", " + upX + ", "
+ upY + ", " + steps +")");
Runnable command = new Runnable() {
@Override
public void run() {
swipe(downX, downY, upX, upY, steps);
}
};
// Collect all accessibility events generated during the swipe command and get the
// last event
ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
runAndWaitForEvents(command,
new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
Configurator.getInstance().getScrollAcknowledgmentTimeout());
AccessibilityEvent event = getLastMatchingEvent(events,
AccessibilityEvent.TYPE_VIEW_SCROLLED);
if (event == null) {
// end of scroll since no new scroll events received
recycleAccessibilityEvents(events);
return false;
}
// AdapterViews have indices we can use to check for the beginning.
boolean foundEnd = false;
if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
foundEnd = event.getFromIndex() == 0 ||
(event.getItemCount() - 1) == event.getToIndex();
Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
} else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
// Determine if we are scrolling vertically or horizontally.
if (downX == upX) {
// Vertical
foundEnd = event.getScrollY() == 0 ||
event.getScrollY() == event.getMaxScrollY();
Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
} else if (downY == upY) {
// Horizontal
foundEnd = event.getScrollX() == 0 ||
event.getScrollX() == event.getMaxScrollX();
Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
}
}
recycleAccessibilityEvents(events);
return !foundEnd;
}
private AccessibilityEvent getLastMatchingEvent(List<AccessibilityEvent> events, int type) {
for (int x = events.size(); x > 0; x--) {
AccessibilityEvent event = events.get(x - 1);
if (event.getEventType() == type)
return event;
}
return null;
}
private void recycleAccessibilityEvents(List<AccessibilityEvent> events) {
for (AccessibilityEvent event : events)
event.recycle();
events.clear();
}
/**
* Handle swipes in any direction.
* @param downX
* @param downY
* @param upX
* @param upY
* @param steps
* @return true if the swipe executed successfully
*/
public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
return swipe(downX, downY, upX, upY, steps, false /*drag*/);
}
/**
* Handle swipes/drags in any direction.
* @param downX
* @param downY
* @param upX
* @param upY
* @param steps
* @param drag when true, the swipe becomes a drag swipe
* @return true if the swipe executed successfully
*/
public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) {
boolean ret = false;
int swipeSteps = steps;
double xStep = 0;
double yStep = 0;
// avoid a divide by zero
if(swipeSteps == 0)
swipeSteps = 1;
xStep = ((double)(upX - downX)) / swipeSteps;
yStep = ((double)(upY - downY)) / swipeSteps;
// first touch starts exactly at the point requested
ret = touchDown(downX, downY);
if (drag)
SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
for(int i = 1; i < swipeSteps; i++) {
ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
if(ret == false)
break;
// set some known constant delay between steps as without it this
// become completely dependent on the speed of the system and results
// may vary on different devices. This guarantees at minimum we have
// a preset delay.
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
}
if (drag)
SystemClock.sleep(REGULAR_CLICK_LENGTH);
ret &= touchUp(upX, upY);
return(ret);
}
/**
* Performs a swipe between points in the Point array.
* @param segments is Point array containing at least one Point object
* @param segmentSteps steps to inject between two Points
* @return true on success
*/
public boolean swipe(Point[] segments, int segmentSteps) {
boolean ret = false;
int swipeSteps = segmentSteps;
double xStep = 0;
double yStep = 0;
// avoid a divide by zero
if(segmentSteps == 0)
segmentSteps = 1;
// must have some points
if(segments.length == 0)
return false;
// first touch starts exactly at the point requested
ret = touchDown(segments[0].x, segments[0].y);
for(int seg = 0; seg < segments.length; seg++) {
if(seg + 1 < segments.length) {
xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps;
yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps;
for(int i = 1; i < swipeSteps; i++) {
ret &= touchMove(segments[seg].x + (int)(xStep * i),
segments[seg].y + (int)(yStep * i));
if(ret == false)
break;
// set some known constant delay between steps as without it this
// become completely dependent on the speed of the system and results
// may vary on different devices. This guarantees at minimum we have
// a preset delay.
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
}
}
}
ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y);
return(ret);
}
public boolean sendText(String text) {
if (DEBUG) {
Log.d(LOG_TAG, "sendText (" + text + ")");
}
KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
if (events != null) {
long keyDelay = Configurator.getInstance().getKeyInjectionDelay();
for (KeyEvent event2 : events) {
// We have to change the time of an event before injecting it because
// all KeyEvents returned by KeyCharacterMap.getEvents() have the same
// time stamp and the system rejects too old events. Hence, it is
// possible for an event to become stale before it is injected if it
// takes too long to inject the preceding ones.
KeyEvent event = KeyEvent.changeTimeRepeat(event2,
SystemClock.uptimeMillis(), 0);
if (!injectEventSync(event)) {
return false;
}
SystemClock.sleep(keyDelay);
}
}
return true;
}
public boolean sendKey(int keyCode, int metaState) {
if (DEBUG) {
Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
}
final long eventTime = SystemClock.uptimeMillis();
KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
if (injectEventSync(downEvent)) {
KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
if(injectEventSync(upEvent)) {
return true;
}
}
return false;
}
/**
* Rotates right and also freezes rotation in that position by
* disabling the sensors. If you want to un-freeze the rotation
* and re-enable the sensors see {@link #unfreezeRotation()}. Note
* that doing so may cause the screen contents to rotate
* depending on the current physical position of the test device.
* @throws RemoteException
*/
public void setRotationRight() {
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
}
/**
* Rotates left and also freezes rotation in that position by
* disabling the sensors. If you want to un-freeze the rotation
* and re-enable the sensors see {@link #unfreezeRotation()}. Note
* that doing so may cause the screen contents to rotate
* depending on the current physical position of the test device.
* @throws RemoteException
*/
public void setRotationLeft() {
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
}
/**
* Rotates up and also freezes rotation in that position by
* disabling the sensors. If you want to un-freeze the rotation
* and re-enable the sensors see {@link #unfreezeRotation()}. Note
* that doing so may cause the screen contents to rotate
* depending on the current physical position of the test device.
* @throws RemoteException
*/
public void setRotationNatural() {
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
}
/**
* Disables the sensors and freezes the device rotation at its
* current rotation state.
* @throws RemoteException
*/
public void freezeRotation() {
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
}
/**
* Re-enables the sensors and un-freezes the device rotation
* allowing its contents to rotate with the device physical rotation.
* @throws RemoteException
*/
public void unfreezeRotation() {
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
}
/**
* This method simply presses the power button if the screen is OFF else
* it does nothing if the screen is already ON.
* @return true if the device was asleep else false
* @throws RemoteException
*/
public boolean wakeDevice() throws RemoteException {
if(!isScreenOn()) {
sendKey(KeyEvent.KEYCODE_POWER, 0);
return true;
}
return false;
}
/**
* This method simply presses the power button if the screen is ON else
* it does nothing if the screen is already OFF.
* @return true if the device was awake else false
* @throws RemoteException
*/
public boolean sleepDevice() throws RemoteException {
if(isScreenOn()) {
this.sendKey(KeyEvent.KEYCODE_POWER, 0);
return true;
}
return false;
}
/**
* Checks the power manager if the screen is ON
* @return true if the screen is ON else false
* @throws RemoteException
*/
public boolean isScreenOn() throws RemoteException {
return mUiAutomatorBridge.isScreenOn();
}
private boolean injectEventSync(InputEvent event) {
return mUiAutomatorBridge.injectInputEvent(event, true);
}
private int getPointerAction(int motionEnvent, int index) {
return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
/**
* Performs a multi-touch gesture
*
* Takes a series of touch coordinates for at least 2 pointers. Each pointer must have
* all of its touch steps defined in an array of {@link PointerCoords}. By having the ability
* to specify the touch points along the path of a pointer, the caller is able to specify
* complex gestures like circles, irregular shapes etc, where each pointer may take a
* different path.
*
* To create a single point on a pointer's touch path
* <code>
* PointerCoords p = new PointerCoords();
* p.x = stepX;
* p.y = stepY;
* p.pressure = 1;
* p.size = 1;
* </code>
* @param touches each array of {@link PointerCoords} constitute a single pointer's touch path.
* Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own
* path. Each {@link PointerCoords} in an array constitute a point on a pointer's path.
* @return <code>true</code> if all points on all paths are injected successfully, <code>false
* </code>otherwise
* @since API Level 18
*/
public boolean performMultiPointerGesture(PointerCoords[] ... touches) {
boolean ret = true;
if (touches.length < 2) {
throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
}
// Get the pointer with the max steps to inject.
int maxSteps = 0;
for (int x = 0; x < touches.length; x++)
maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps;
// specify the properties for each pointer as finger touch
PointerProperties[] properties = new PointerProperties[touches.length];
PointerCoords[] pointerCoords = new PointerCoords[touches.length];
for (int x = 0; x < touches.length; x++) {
PointerProperties prop = new PointerProperties();
prop.id = x;
prop.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[x] = prop;
// for each pointer set the first coordinates for touch down
pointerCoords[x] = touches[x][0];
}
// Touch down all pointers
long downTime = SystemClock.uptimeMillis();
MotionEvent event;
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
}
// Move all pointers
for (int i = 1; i < maxSteps - 1; i++) {
// for each pointer
for (int x = 0; x < touches.length; x++) {
// check if it has coordinates to move
if (touches[x].length > i)
pointerCoords[x] = touches[x][i];
else
pointerCoords[x] = touches[x][touches[x].length - 1];
}
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
}
// For each pointer get the last coordinates
for (int x = 0; x < touches.length; x++)
pointerCoords[x] = touches[x][touches[x].length - 1];
// touch up
for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
}
Log.i(LOG_TAG, "x " + pointerCoords[0].x);
// first to touch down is last up
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
return ret;
}
/**
* Simulates a short press on the Recent Apps button.
*
* @return true if successful, else return false
* @since API Level 18
*/
public boolean toggleRecentApps() {
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_RECENTS);
}
/**
* Opens the notification shade
*
* @return true if successful, else return false
* @since API Level 18
*/
public boolean openNotification() {
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
}
/**
* Opens the quick settings shade
*
* @return true if successful, else return false
* @since API Level 18
*/
public boolean openQuickSettings() {
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
}
}

View File

@ -0,0 +1,521 @@
/*
* 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 com.android.uiautomator.core;
import android.app.UiAutomation.OnAccessibilityEventListener;
import android.os.SystemClock;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* The QueryController main purpose is to translate a {@link UiSelector} selectors to
* {@link AccessibilityNodeInfo}. This is all this controller does.
*/
class QueryController {
private static final String LOG_TAG = QueryController.class.getSimpleName();
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private static final boolean VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE);
private final UiAutomatorBridge mUiAutomatorBridge;
private final Object mLock = new Object();
private String mLastActivityName = null;
// During a pattern selector search, the recursive pattern search
// methods will track their counts and indexes here.
private int mPatternCounter = 0;
private int mPatternIndexer = 0;
// These help show each selector's search context as it relates to the previous sub selector
// matched. When a compound selector fails, it is hard to tell which part of it is failing.
// Seeing how a selector is being parsed and which sub selector failed within a long list
// of compound selectors is very helpful.
private int mLogIndent = 0;
private int mLogParentIndent = 0;
private String mLastTraversedText = "";
public QueryController(UiAutomatorBridge bridge) {
mUiAutomatorBridge = bridge;
bridge.setOnAccessibilityEventListener(new OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
switch(event.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
// don't trust event.getText(), check for nulls
if (event.getText() != null && event.getText().size() > 0) {
if(event.getText().get(0) != null)
mLastActivityName = event.getText().get(0).toString();
}
break;
case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
// don't trust event.getText(), check for nulls
if (event.getText() != null && event.getText().size() > 0)
if(event.getText().get(0) != null)
mLastTraversedText = event.getText().get(0).toString();
if (DEBUG)
Log.d(LOG_TAG, "Last text selection reported: " +
mLastTraversedText);
break;
}
mLock.notifyAll();
}
}
});
}
/**
* Returns the last text selection reported by accessibility
* event TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY. One way to cause
* this event is using a DPad arrows to focus on UI elements.
*/
public String getLastTraversedText() {
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
if (mLastTraversedText.length() > 0) {
return mLastTraversedText;
}
}
return null;
}
/**
* Clears the last text selection value saved from the TYPE_VIEW_TEXT_SELECTION_CHANGED
* event
*/
public void clearLastTraversedText() {
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
mLastTraversedText = "";
}
}
private void initializeNewSearch() {
mPatternCounter = 0;
mPatternIndexer = 0;
mLogIndent = 0;
mLogParentIndent = 0;
}
/**
* Counts the instances of the selector group. The selector must be in the following
* format: [container_selector, PATTERN=[INSTANCE=x, PATTERN=[the_pattern]]
* where the container_selector is used to find the containment region to search for patterns
* and the INSTANCE=x is the instance of the_pattern to return.
* @param selector
* @return number of pattern matches. Returns 0 for all other cases.
*/
public int getPatternCount(UiSelector selector) {
findAccessibilityNodeInfo(selector, true /*counting*/);
return mPatternCounter;
}
/**
* Main search method for translating By selectors to AccessibilityInfoNodes
* @param selector
* @return AccessibilityNodeInfo
*/
public AccessibilityNodeInfo findAccessibilityNodeInfo(UiSelector selector) {
return findAccessibilityNodeInfo(selector, false);
}
protected AccessibilityNodeInfo findAccessibilityNodeInfo(UiSelector selector,
boolean isCounting) {
mUiAutomatorBridge.waitForIdle();
initializeNewSearch();
if (DEBUG)
Log.d(LOG_TAG, "Searching: " + selector);
synchronized (mLock) {
AccessibilityNodeInfo rootNode = getRootNode();
if (rootNode == null) {
Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search");
return null;
}
// Copy so that we don't modify the original's sub selectors
UiSelector uiSelector = new UiSelector(selector);
return translateCompoundSelector(uiSelector, rootNode, isCounting);
}
}
/**
* Gets the root node from accessibility and if it fails to get one it will
* retry every 250ms for up to 1000ms.
* @return null if no root node is obtained
*/
protected AccessibilityNodeInfo getRootNode() {
final int maxRetry = 4;
final long waitInterval = 250;
AccessibilityNodeInfo rootNode = null;
for(int x = 0; x < maxRetry; x++) {
rootNode = mUiAutomatorBridge.getRootInActiveWindow();
if (rootNode != null) {
return rootNode;
}
if(x < maxRetry - 1) {
Log.e(LOG_TAG, "Got null root node from accessibility - Retrying...");
SystemClock.sleep(waitInterval);
}
}
return rootNode;
}
/**
* A compoundSelector encapsulate both Regular and Pattern selectors. The formats follows:
* <p/>
* regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]]
* <br/>
* pattern_selector = ...CONTAINER=By[..] PATTERN=By[instance=x PATTERN=[regular_selector]
* <br/>
* compound_selector = [regular_selector [pattern_selector]]
* <p/>
* regular_selectors are the most common form of selectors and the search for them
* is straightforward. On the other hand pattern_selectors requires search to be
* performed as in regular_selector but where regular_selector search returns immediately
* upon a successful match, the search for pattern_selector continues until the
* requested matched _instance_ of that pattern is matched.
* <p/>
* Counting UI objects requires using pattern_selectors. The counting search is the same
* as a pattern_search however we're not looking to match an instance of the pattern but
* rather continuously walking the accessibility node hierarchy while counting matched
* patterns, until the end of the tree.
* <p/>
* If both present, order of parsing begins with CONTAINER followed by PATTERN then the
* top most selector is processed as regular_selector within the context of the previous
* CONTAINER and its PATTERN information. If neither is present then the top selector is
* directly treated as regular_selector. So the presence of a CONTAINER and PATTERN within
* a selector simply dictates that the selector matching will be constraint to the sub tree
* node where the CONTAINER and its child PATTERN have identified.
* @param selector
* @param fromNode
* @param isCounting
* @return AccessibilityNodeInfo
*/
private AccessibilityNodeInfo translateCompoundSelector(UiSelector selector,
AccessibilityNodeInfo fromNode, boolean isCounting) {
// Start translating compound selectors by translating the regular_selector first
// The regular_selector is then used as a container for any optional pattern_selectors
// that may or may not be specified.
if(selector.hasContainerSelector())
// nested pattern selectors
if(selector.getContainerSelector().hasContainerSelector()) {
fromNode = translateCompoundSelector(
selector.getContainerSelector(), fromNode, false);
initializeNewSearch();
} else
fromNode = translateReqularSelector(selector.getContainerSelector(), fromNode);
else
fromNode = translateReqularSelector(selector, fromNode);
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Container selector not found: " + selector.dumpToString(false));
return null;
}
if(selector.hasPatternSelector()) {
fromNode = translatePatternSelector(selector.getPatternSelector(),
fromNode, isCounting);
if (isCounting) {
Log.i(LOG_TAG, String.format(
"Counted %d instances of: %s", mPatternCounter, selector));
return null;
} else {
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Pattern selector not found: " +
selector.dumpToString(false));
return null;
}
}
}
// translate any additions to the selector that may have been added by tests
// with getChild(By selector) after a container and pattern selectors
if(selector.hasContainerSelector() || selector.hasPatternSelector()) {
if(selector.hasChildSelector() || selector.hasParentSelector())
fromNode = translateReqularSelector(selector, fromNode);
}
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Object Not Found for selector " + selector);
return null;
}
Log.i(LOG_TAG, String.format("Matched selector: %s <<==>> [%s]", selector, fromNode));
return fromNode;
}
/**
* Used by the {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
* to translate the regular_selector portion. It has the following format:
* <p/>
* regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]]<br/>
* <p/>
* regular_selectors are the most common form of selectors and the search for them
* is straightforward. This method will only look for CHILD or PARENT sub selectors.
* <p/>
* @param selector
* @param fromNode
* @return AccessibilityNodeInfo if found else null
*/
private AccessibilityNodeInfo translateReqularSelector(UiSelector selector,
AccessibilityNodeInfo fromNode) {
return findNodeRegularRecursive(selector, fromNode, 0);
}
private AccessibilityNodeInfo findNodeRegularRecursive(UiSelector subSelector,
AccessibilityNodeInfo fromNode, int index) {
if (subSelector.isMatchFor(fromNode, index)) {
if (DEBUG) {
Log.d(LOG_TAG, formatLog(String.format("%s",
subSelector.dumpToString(false))));
}
if(subSelector.isLeaf()) {
return fromNode;
}
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null; // there is an implementation fault
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null; // there is an implementation fault
}
// the selector requested we start at this level from
// the parent node from the one we just matched
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (VERBOSE)
Log.v(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodeRegularRecursive(subSelector, childNode, i);
if (retNode != null) {
return retNode;
}
}
return null;
}
/**
* Used by the {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
* to translate the pattern_selector portion. It has the following format:
* <p/>
* pattern_selector = ... PATTERN=By[instance=x PATTERN=[regular_selector]]<br/>
* <p/>
* pattern_selectors requires search to be performed as regular_selector but where
* regular_selector search returns immediately upon a successful match, the search for
* pattern_selector continues until the requested matched instance of that pattern is
* encountered.
* <p/>
* Counting UI objects requires using pattern_selectors. The counting search is the same
* as a pattern_search however we're not looking to match an instance of the pattern but
* rather continuously walking the accessibility node hierarchy while counting patterns
* until the end of the tree.
* @param subSelector
* @param fromNode
* @param isCounting
* @return null of node is not found or if counting mode is true.
* See {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
*/
private AccessibilityNodeInfo translatePatternSelector(UiSelector subSelector,
AccessibilityNodeInfo fromNode, boolean isCounting) {
if(subSelector.hasPatternSelector()) {
// Since pattern_selectors are also the type of selectors used when counting,
// we check if this is a counting run or an indexing run
if(isCounting)
//since we're counting, we reset the indexer so to terminates the search when
// the end of tree is reached. The count will be in mPatternCount
mPatternIndexer = -1;
else
// terminates the search once we match the pattern's instance
mPatternIndexer = subSelector.getInstance();
// A pattern is wrapped in a PATTERN[instance=x PATTERN[the_pattern]]
subSelector = subSelector.getPatternSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Pattern portion of the selector is null or not defined");
return null; // there is an implementation fault
}
// save the current indent level as parent indent before pattern searches
// begin under the current tree position.
mLogParentIndent = ++mLogIndent;
return findNodePatternRecursive(subSelector, fromNode, 0, subSelector);
}
Log.e(LOG_TAG, "Selector must have a pattern selector defined"); // implementation fault?
return null;
}
private AccessibilityNodeInfo findNodePatternRecursive(
UiSelector subSelector, AccessibilityNodeInfo fromNode, int index,
UiSelector originalPattern) {
if (subSelector.isMatchFor(fromNode, index)) {
if(subSelector.isLeaf()) {
if(mPatternIndexer == 0) {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
return fromNode;
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
mPatternCounter++; //count the pattern matched
mPatternIndexer--; //decrement until zero for the instance requested
// At a leaf selector within a group and still not instance matched
// then reset the selector to continue search from current position
// in the accessibility tree for the next pattern match up until the
// pattern index hits 0.
subSelector = originalPattern;
// starting over with next pattern search so reset to parent level
mLogIndent = mLogParentIndent;
}
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null;
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null;
}
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (DEBUG)
Log.d(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodePatternRecursive(
subSelector, childNode, i, originalPattern);
if (retNode != null) {
return retNode;
}
}
return null;
}
public AccessibilityNodeInfo getAccessibilityRootNode() {
return mUiAutomatorBridge.getRootInActiveWindow();
}
/**
* Last activity to report accessibility events.
* @deprecated The results returned should be considered unreliable
* @return String name of activity
*/
@Deprecated
public String getCurrentActivityName() {
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
return mLastActivityName;
}
}
/**
* Last package to report accessibility events
* @return String name of package
*/
public String getCurrentPackageName() {
mUiAutomatorBridge.waitForIdle();
AccessibilityNodeInfo rootNode = getRootNode();
if (rootNode == null)
return null;
return rootNode.getPackageName() != null ? rootNode.getPackageName().toString() : null;
}
private String formatLog(String str) {
StringBuilder l = new StringBuilder();
for(int space = 0; space < mLogIndent; space++)
l.append(". . ");
if(mLogIndent > 0)
l.append(String.format(". . [%d]: %s", mPatternCounter, str));
else
l.append(String.format(". . [%d]: %s", mPatternCounter, str));
return l.toString();
}
}

View File

@ -0,0 +1,285 @@
/*
* 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 com.android.uiautomator.core;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* Class that creates traces of the calls to the UiAutomator API and outputs the
* traces either to logcat or a logfile. Each public method in the UiAutomator
* that needs to be traced should include a call to Tracer.trace in the
* beginning. Tracing is turned off by defualt and needs to be enabled
* explicitly.
* @hide
*/
public class Tracer {
private static final String UNKNOWN_METHOD_STRING = "(unknown method)";
private static final String UIAUTOMATOR_PACKAGE = "com.android.uiautomator.core";
private static final int CALLER_LOCATION = 6;
private static final int METHOD_TO_TRACE_LOCATION = 5;
private static final int MIN_STACK_TRACE_LENGTH = 7;
/**
* Enum that determines where the trace output goes. It can go to either
* logcat, log file or both.
*/
public enum Mode {
NONE,
FILE,
LOGCAT,
ALL
}
private interface TracerSink {
public void log(String message);
public void close();
}
private class FileSink implements TracerSink {
private PrintWriter mOut;
private SimpleDateFormat mDateFormat;
public FileSink(File file) throws FileNotFoundException {
mOut = new PrintWriter(file);
mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
}
public void log(String message) {
mOut.printf("%s %s\n", mDateFormat.format(new Date()), message);
}
public void close() {
mOut.close();
}
}
private class LogcatSink implements TracerSink {
private static final String LOGCAT_TAG = "UiAutomatorTrace";
public void log(String message) {
Log.i(LOGCAT_TAG, message);
}
public void close() {
// nothing is needed
}
}
private Mode mCurrentMode = Mode.NONE;
private List<TracerSink> mSinks = new ArrayList<TracerSink>();
private File mOutputFile;
private static Tracer mInstance = null;
/**
* Returns a reference to an instance of the tracer. Useful to set the
* parameters before the trace is collected.
*
* @return
*/
public static Tracer getInstance() {
if (mInstance == null) {
mInstance = new Tracer();
}
return mInstance;
}
/**
* Sets where the trace output will go. Can be either be logcat or a file or
* both. Setting this to NONE will turn off tracing.
*
* @param mode
*/
public void setOutputMode(Mode mode) {
closeSinks();
mCurrentMode = mode;
try {
switch (mode) {
case FILE:
if (mOutputFile == null) {
throw new IllegalArgumentException("Please provide a filename before " +
"attempting write trace to a file");
}
mSinks.add(new FileSink(mOutputFile));
break;
case LOGCAT:
mSinks.add(new LogcatSink());
break;
case ALL:
mSinks.add(new LogcatSink());
if (mOutputFile == null) {
throw new IllegalArgumentException("Please provide a filename before " +
"attempting write trace to a file");
}
mSinks.add(new FileSink(mOutputFile));
break;
default:
break;
}
} catch (FileNotFoundException e) {
Log.w("Tracer", "Could not open log file: " + e.getMessage());
}
}
private void closeSinks() {
for (TracerSink sink : mSinks) {
sink.close();
}
mSinks.clear();
}
/**
* Sets the name of the log file where tracing output will be written if the
* tracer is set to write to a file.
*
* @param filename name of the log file.
*/
public void setOutputFilename(String filename) {
mOutputFile = new File(filename);
}
private void doTrace(Object[] arguments) {
if (mCurrentMode == Mode.NONE) {
return;
}
String caller = getCaller();
if (caller == null) {
return;
}
log(String.format("%s (%s)", caller, join(", ", arguments)));
}
private void log(String message) {
for (TracerSink sink : mSinks) {
sink.log(message);
}
}
/**
* Queries whether the tracing is enabled.
* @return true if tracing is enabled, false otherwise.
*/
public boolean isTracingEnabled() {
return mCurrentMode != Mode.NONE;
}
/**
* Public methods in the UiAutomator should call this function to generate a
* trace. The trace will include the method thats is being called, it's
* arguments and where in the user's code the method is called from. If a
* public method is called internally from UIAutomator then this will not
* output a trace entry. Only calls from outise the UiAutomator package will
* produce output.
*
* Special note about array arguments. You can safely pass arrays of reference types
* to this function. Like String[] or Integer[]. The trace function will print their
* contents by calling toString() on each of the elements. This will not work for
* array of primitive types like int[] or float[]. Before passing them to this function
* convert them to arrays of reference types manually. Example: convert int[] to Integer[].
*
* @param arguments arguments of the method being traced.
*/
public static void trace(Object... arguments) {
Tracer.getInstance().doTrace(arguments);
}
private static String join(String separator, Object[] strings) {
if (strings.length == 0)
return "";
StringBuilder builder = new StringBuilder(objectToString(strings[0]));
for (int i = 1; i < strings.length; i++) {
builder.append(separator);
builder.append(objectToString(strings[i]));
}
return builder.toString();
}
/**
* Special toString method to handle arrays. If the argument is a normal object then this will
* return normal output of obj.toString(). If the argument is an array this will return a
* string representation of the elements of the array.
*
* This method will not work for arrays of primitive types. Arrays of primitive types are
* expected to be converted manually by the caller. If the array is not converter then
* this function will only output "[...]" instead of the contents of the array.
*
* @param obj object to convert to a string
* @return String representation of the object.
*/
private static String objectToString(Object obj) {
if (obj.getClass().isArray()) {
if (obj instanceof Object[]) {
return Arrays.deepToString((Object[])obj);
} else {
return "[...]";
}
} else {
return obj.toString();
}
}
/**
* This method outputs which UiAutomator method was called and where in the
* user code it was called from. If it can't deside which method is called
* it will output "(unknown method)". If the method was called from inside
* the UiAutomator then it returns null.
*
* @return name of the method called and where it was called from. Null if
* method was called from inside UiAutomator.
*/
private static String getCaller() {
StackTraceElement stackTrace[] = Thread.currentThread().getStackTrace();
if (stackTrace.length < MIN_STACK_TRACE_LENGTH) {
return UNKNOWN_METHOD_STRING;
}
StackTraceElement caller = stackTrace[METHOD_TO_TRACE_LOCATION];
StackTraceElement previousCaller = stackTrace[CALLER_LOCATION];
if (previousCaller.getClassName().startsWith(UIAUTOMATOR_PACKAGE)) {
return null;
}
int indexOfDot = caller.getClassName().lastIndexOf('.');
if (indexOfDot < 0) {
indexOfDot = 0;
}
if (indexOfDot + 1 >= caller.getClassName().length()) {
return UNKNOWN_METHOD_STRING;
}
String shortClassName = caller.getClassName().substring(indexOfDot + 1);
return String.format("%s.%s from %s() at %s:%d", shortClassName, caller.getMethodName(),
previousCaller.getMethodName(), previousCaller.getFileName(),
previousCaller.getLineNumber());
}
}

View File

@ -0,0 +1,143 @@
package com.android.uiautomator.core;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.UiAutomation;
import android.app.UiAutomation.AccessibilityEventFilter;
import android.app.UiAutomation.OnAccessibilityEventListener;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.Display;
import android.view.InputEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @hide
*/
public abstract class UiAutomatorBridge {
private static final String LOG_TAG = UiAutomatorBridge.class.getSimpleName();
/**
* This value has the greatest bearing on the appearance of test execution speeds.
* This value is used as the minimum time to wait before considering the UI idle after
* each action.
*/
private static final long QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE = 500;//ms
/**
* This is the maximum time the automation will wait for the UI to go idle. Execution
* will resume normally anyway. This is to prevent waiting forever on display updates
* that may be related to spinning wheels or progress updates of sorts etc...
*/
private static final long TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE = 1000 * 10;//ms
private final UiAutomation mUiAutomation;
private final InteractionController mInteractionController;
private final QueryController mQueryController;
UiAutomatorBridge(UiAutomation uiAutomation) {
mUiAutomation = uiAutomation;
mInteractionController = new InteractionController(this);
mQueryController = new QueryController(this);
}
InteractionController getInteractionController() {
return mInteractionController;
}
QueryController getQueryController() {
return mQueryController;
}
public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
mUiAutomation.setOnAccessibilityEventListener(listener);
}
public AccessibilityNodeInfo getRootInActiveWindow() {
return mUiAutomation.getRootInActiveWindow();
}
public boolean injectInputEvent(InputEvent event, boolean sync) {
return mUiAutomation.injectInputEvent(event, sync);
}
public boolean setRotation(int rotation) {
return mUiAutomation.setRotation(rotation);
}
public void setCompressedLayoutHierarchy(boolean compressed) {
AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
if (compressed)
info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
else
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
mUiAutomation.setServiceInfo(info);
}
public abstract int getRotation();
public abstract boolean isScreenOn();
public void waitForIdle() {
waitForIdle(TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE);
}
public void waitForIdle(long timeout) {
try {
mUiAutomation.waitForIdle(QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE, timeout);
} catch (TimeoutException te) {
Log.w(LOG_TAG, "Could not detect idle state.", te);
}
}
public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
return mUiAutomation.executeAndWaitForEvent(command,
filter, timeoutMillis);
}
public boolean takeScreenshot(File storePath, int quality) {
Bitmap screenshot = mUiAutomation.takeScreenshot();
if (screenshot == null) {
return false;
}
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(storePath));
if (bos != null) {
screenshot.compress(Bitmap.CompressFormat.PNG, quality, bos);
bos.flush();
}
} catch (IOException ioe) {
Log.e(LOG_TAG, "failed to save screen shot to file", ioe);
return false;
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException ioe) {
/* ignore */
}
}
screenshot.recycle();
}
return true;
}
public boolean performGlobalAction(int action) {
return mUiAutomation.performGlobalAction(action);
}
public abstract Display getDefaultDisplay();
public abstract long getSystemLongPressTime();
}

View File

@ -0,0 +1,145 @@
/*
* 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 com.android.uiautomator.core;
/**
* Used to enumerate a container's UI elements for the purpose of counting,
* or targeting a sub elements by a child's text or description.
* @since API Level 16
*/
public class UiCollection extends UiObject {
/**
* Constructs an instance as described by the selector
*
* @param selector
* @since API Level 16
*/
public UiCollection(UiSelector selector) {
super(selector);
}
/**
* Searches for child UI element within the constraints of this UiCollection {@link UiSelector}
* selector.
*
* It looks for any child matching the <code>childPattern</code> argument that has
* a child UI element anywhere within its sub hierarchy that has content-description text.
* The returned UiObject will point at the <code>childPattern</code> instance that matched the
* search and not at the identifying child element that matched the content description.</p>
*
* @param childPattern {@link UiSelector} selector of the child pattern to match and return
* @param text String of the identifying child contents of of the <code>childPattern</code>
* @return {@link UiObject} pointing at and instance of <code>childPattern</code>
* @throws UiObjectNotFoundException
* @since API Level 16
*/
public UiObject getChildByDescription(UiSelector childPattern, String text)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, text);
if (text != null) {
int count = getChildCount(childPattern);
for (int x = 0; x < count; x++) {
UiObject row = getChildByInstance(childPattern, x);
String nodeDesc = row.getContentDescription();
if(nodeDesc != null && nodeDesc.contains(text)) {
return row;
}
UiObject item = row.getChild(new UiSelector().descriptionContains(text));
if (item.exists()) {
return row;
}
}
}
throw new UiObjectNotFoundException("for description= \"" + text + "\"");
}
/**
* Searches for child UI element within the constraints of this UiCollection {@link UiSelector}
* selector.
*
* It looks for any child matching the <code>childPattern</code> argument that has
* a child UI element anywhere within its sub hierarchy that is at the <code>instance</code>
* specified. The operation is performed only on the visible items and no scrolling is performed
* in this case.
*
* @param childPattern {@link UiSelector} selector of the child pattern to match and return
* @param instance int the desired matched instance of this <code>childPattern</code>
* @return {@link UiObject} pointing at and instance of <code>childPattern</code>
* @since API Level 16
*/
public UiObject getChildByInstance(UiSelector childPattern, int instance)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, instance);
UiSelector patternSelector = UiSelector.patternBuilder(getSelector(),
UiSelector.patternBuilder(childPattern).instance(instance));
return new UiObject(patternSelector);
}
/**
* Searches for child UI element within the constraints of this UiCollection {@link UiSelector}
* selector.
*
* It looks for any child matching the <code>childPattern</code> argument that has
* a child UI element anywhere within its sub hierarchy that has text attribute =
* <code>text</code>. The returned UiObject will point at the <code>childPattern</code>
* instance that matched the search and not at the identifying child element that matched the
* text attribute.</p>
*
* @param childPattern {@link UiSelector} selector of the child pattern to match and return
* @param text String of the identifying child contents of of the <code>childPattern</code>
* @return {@link UiObject} pointing at and instance of <code>childPattern</code>
* @throws UiObjectNotFoundException
* @since API Level 16
*/
public UiObject getChildByText(UiSelector childPattern, String text)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, text);
if (text != null) {
int count = getChildCount(childPattern);
for (int x = 0; x < count; x++) {
UiObject row = getChildByInstance(childPattern, x);
String nodeText = row.getText();
if(text.equals(nodeText)) {
return row;
}
UiObject item = row.getChild(new UiSelector().text(text));
if (item.exists()) {
return row;
}
}
}
throw new UiObjectNotFoundException("for text= \"" + text + "\"");
}
/**
* Counts child UI element instances matching the <code>childPattern</code>
* argument. The method returns the number of matching UI elements that are
* currently visible. The count does not include items of a scrollable list
* that are off-screen.
*
* @param childPattern a {@link UiSelector} that represents the matching child UI
* elements to count
* @return the number of matched childPattern under the current {@link UiCollection}
* @since API Level 16
*/
public int getChildCount(UiSelector childPattern) {
Tracer.trace(childPattern);
UiSelector patternSelector =
UiSelector.patternBuilder(getSelector(), UiSelector.patternBuilder(childPattern));
return getQueryController().getPatternCount(patternSelector);
}
}

View File

@ -0,0 +1,851 @@
/*
* 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 com.android.uiautomator.core;
import android.app.UiAutomation;
import android.app.UiAutomation.AccessibilityEventFilter;
import android.graphics.Point;
import android.os.Build;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* UiDevice provides access to state information about the device.
* You can also use this class to simulate user actions on the device,
* such as pressing the d-pad or pressing the Home and Menu buttons.
* @since API Level 16
*/
public class UiDevice {
private static final String LOG_TAG = UiDevice.class.getSimpleName();
// Sometimes HOME and BACK key presses will generate no events if already on
// home page or there is nothing to go back to, Set low timeouts.
private static final long KEY_PRESS_EVENT_TIMEOUT = 1 * 1000;
// store for registered UiWatchers
private final HashMap<String, UiWatcher> mWatchers = new HashMap<String, UiWatcher>();
private final List<String> mWatchersTriggers = new ArrayList<String>();
// remember if we're executing in the context of a UiWatcher
private boolean mInWatcherContext = false;
// provides access the {@link QueryController} and {@link InteractionController}
private UiAutomatorBridge mUiAutomationBridge;
// reference to self
private static UiDevice sDevice;
private UiDevice() {
/* hide constructor */
}
/**
* @hide
*/
public void initialize(UiAutomatorBridge uiAutomatorBridge) {
mUiAutomationBridge = uiAutomatorBridge;
}
boolean isInWatcherContext() {
return mInWatcherContext;
}
/**
* Provides access the {@link QueryController} and {@link InteractionController}
* @return {@link ShellUiAutomatorBridge}
*/
UiAutomatorBridge getAutomatorBridge() {
if (mUiAutomationBridge == null) {
throw new RuntimeException("UiDevice not initialized");
}
return mUiAutomationBridge;
}
/**
* Enables or disables layout hierarchy compression.
*
* If compression is enabled, the layout hierarchy derived from the Acessibility
* framework will only contain nodes that are important for uiautomator
* testing. Any unnecessary surrounding layout nodes that make viewing
* and searching the hierarchy inefficient are removed.
*
* @param compressed true to enable compression; else, false to disable
* @since API Level 18
*/
public void setCompressedLayoutHeirarchy(boolean compressed) {
getAutomatorBridge().setCompressedLayoutHierarchy(compressed);
}
/**
* Retrieves a singleton instance of UiDevice
*
* @return UiDevice instance
* @since API Level 16
*/
public static UiDevice getInstance() {
if (sDevice == null) {
sDevice = new UiDevice();
}
return sDevice;
}
/**
* Returns the display size in dp (device-independent pixel)
*
* The returned display size is adjusted per screen rotation. Also this will return the actual
* size of the screen, rather than adjusted per system decorations (like status bar).
*
* @return a Point containing the display size in dp
*/
public Point getDisplaySizeDp() {
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
display.getRealSize(p);
DisplayMetrics metrics = new DisplayMetrics();
display.getRealMetrics(metrics);
float dpx = p.x / metrics.density;
float dpy = p.y / metrics.density;
p.x = Math.round(dpx);
p.y = Math.round(dpy);
return p;
}
/**
* Retrieves the product name of the device.
*
* This method provides information on what type of device the test is running on. This value is
* the same as returned by invoking #adb shell getprop ro.product.name.
*
* @return product name of the device
* @since API Level 17
*/
public String getProductName() {
Tracer.trace();
return Build.PRODUCT;
}
/**
* Retrieves the text from the last UI traversal event received.
*
* You can use this method to read the contents in a WebView container
* because the accessibility framework fires events
* as each text is highlighted. You can write a test to perform
* directional arrow presses to focus on different elements inside a WebView,
* and call this method to get the text from each traversed element.
* If you are testing a view container that can return a reference to a
* Document Object Model (DOM) object, your test should use the view's
* DOM instead.
*
* @return text of the last traversal event, else return an empty string
* @since API Level 16
*/
public String getLastTraversedText() {
Tracer.trace();
return getAutomatorBridge().getQueryController().getLastTraversedText();
}
/**
* Clears the text from the last UI traversal event.
* See {@link #getLastTraversedText()}.
* @since API Level 16
*/
public void clearLastTraversedText() {
Tracer.trace();
getAutomatorBridge().getQueryController().clearLastTraversedText();
}
/**
* Simulates a short press on the MENU button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressMenu() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
KeyEvent.KEYCODE_MENU, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
KEY_PRESS_EVENT_TIMEOUT);
}
/**
* Simulates a short press on the BACK button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressBack() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
KeyEvent.KEYCODE_BACK, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
KEY_PRESS_EVENT_TIMEOUT);
}
/**
* Simulates a short press on the HOME button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressHome() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
KEY_PRESS_EVENT_TIMEOUT);
}
/**
* Simulates a short press on the SEARCH button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressSearch() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_SEARCH);
}
/**
* Simulates a short press on the CENTER button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDPadCenter() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
}
/**
* Simulates a short press on the DOWN button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDPadDown() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
}
/**
* Simulates a short press on the UP button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDPadUp() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DPAD_UP);
}
/**
* Simulates a short press on the LEFT button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDPadLeft() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);
}
/**
* Simulates a short press on the RIGHT button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDPadRight() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);
}
/**
* Simulates a short press on the DELETE key.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressDelete() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_DEL);
}
/**
* Simulates a short press on the ENTER key.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressEnter() {
Tracer.trace();
return pressKeyCode(KeyEvent.KEYCODE_ENTER);
}
/**
* Simulates a short press using a key code.
*
* See {@link KeyEvent}
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressKeyCode(int keyCode) {
Tracer.trace(keyCode);
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKey(keyCode, 0);
}
/**
* Simulates a short press using a key code.
*
* See {@link KeyEvent}.
* @param keyCode the key code of the event.
* @param metaState an integer in which each bit set to 1 represents a pressed meta key
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressKeyCode(int keyCode, int metaState) {
Tracer.trace(keyCode, metaState);
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKey(keyCode, metaState);
}
/**
* Simulates a short press on the Recent Apps button.
*
* @return true if successful, else return false
* @throws RemoteException
* @since API Level 16
*/
public boolean pressRecentApps() throws RemoteException {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().toggleRecentApps();
}
/**
* Opens the notification shade.
*
* @return true if successful, else return false
* @since API Level 18
*/
public boolean openNotification() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().openNotification();
}
/**
* Opens the Quick Settings shade.
*
* @return true if successful, else return false
* @since API Level 18
*/
public boolean openQuickSettings() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().openQuickSettings();
}
/**
* Gets the width of the display, in pixels. The width and height details
* are reported based on the current orientation of the display.
* @return width in pixels or zero on failure
* @since API Level 16
*/
public int getDisplayWidth() {
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
display.getSize(p);
return p.x;
}
/**
* Gets the height of the display, in pixels. The size is adjusted based
* on the current orientation of the display.
* @return height in pixels or zero on failure
* @since API Level 16
*/
public int getDisplayHeight() {
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
display.getSize(p);
return p.y;
}
/**
* Perform a click at arbitrary coordinates specified by the user
*
* @param x coordinate
* @param y coordinate
* @return true if the click succeeded else false
* @since API Level 16
*/
public boolean click(int x, int y) {
Tracer.trace(x, y);
if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
return (false);
}
return getAutomatorBridge().getInteractionController().clickNoSync(x, y);
}
/**
* Performs a swipe from one coordinate to another using the number of steps
* to determine smoothness and speed. Each step execution is throttled to 5ms
* per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
*
* @param startX
* @param startY
* @param endX
* @param endY
* @param steps is the number of move steps sent to the system
* @return false if the operation fails or the coordinates are invalid
* @since API Level 16
*/
public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
Tracer.trace(startX, startY, endX, endY, steps);
return getAutomatorBridge().getInteractionController()
.swipe(startX, startY, endX, endY, steps);
}
/**
* Performs a swipe from one coordinate to another coordinate. You can control
* the smoothness and speed of the swipe by specifying the number of steps.
* Each step execution is throttled to 5 milliseconds per step, so for a 100
* steps, the swipe will take around 0.5 seconds to complete.
*
* @param startX X-axis value for the starting coordinate
* @param startY Y-axis value for the starting coordinate
* @param endX X-axis value for the ending coordinate
* @param endY Y-axis value for the ending coordinate
* @param steps is the number of steps for the swipe action
* @return true if swipe is performed, false if the operation fails
* or the coordinates are invalid
* @since API Level 18
*/
public boolean drag(int startX, int startY, int endX, int endY, int steps) {
Tracer.trace(startX, startY, endX, endY, steps);
return getAutomatorBridge().getInteractionController()
.swipe(startX, startY, endX, endY, steps, true);
}
/**
* Performs a swipe between points in the Point array. Each step execution is throttled
* to 5ms per step. So for a 100 steps, the swipe will take about 1/2 second to complete
*
* @param segments is Point array containing at least one Point object
* @param segmentSteps steps to inject between two Points
* @return true on success
* @since API Level 16
*/
public boolean swipe(Point[] segments, int segmentSteps) {
Tracer.trace(segments, segmentSteps);
return getAutomatorBridge().getInteractionController().swipe(segments, segmentSteps);
}
/**
* Waits for the current application to idle.
* Default wait timeout is 10 seconds
* @since API Level 16
*/
public void waitForIdle() {
Tracer.trace();
waitForIdle(Configurator.getInstance().getWaitForIdleTimeout());
}
/**
* Waits for the current application to idle.
* @param timeout in milliseconds
* @since API Level 16
*/
public void waitForIdle(long timeout) {
Tracer.trace(timeout);
getAutomatorBridge().waitForIdle(timeout);
}
/**
* Retrieves the last activity to report accessibility events.
* @deprecated The results returned should be considered unreliable
* @return String name of activity
* @since API Level 16
*/
@Deprecated
public String getCurrentActivityName() {
Tracer.trace();
return getAutomatorBridge().getQueryController().getCurrentActivityName();
}
/**
* Retrieves the name of the last package to report accessibility events.
* @return String name of package
* @since API Level 16
*/
public String getCurrentPackageName() {
Tracer.trace();
return getAutomatorBridge().getQueryController().getCurrentPackageName();
}
/**
* Registers a {@link UiWatcher} to run automatically when the testing framework is unable to
* find a match using a {@link UiSelector}. See {@link #runWatchers()}
*
* @param name to register the UiWatcher
* @param watcher {@link UiWatcher}
* @since API Level 16
*/
public void registerWatcher(String name, UiWatcher watcher) {
Tracer.trace(name, watcher);
if (mInWatcherContext) {
throw new IllegalStateException("Cannot register new watcher from within another");
}
mWatchers.put(name, watcher);
}
/**
* Removes a previously registered {@link UiWatcher}.
*
* See {@link #registerWatcher(String, UiWatcher)}
* @param name used to register the UiWatcher
* @since API Level 16
*/
public void removeWatcher(String name) {
Tracer.trace(name);
if (mInWatcherContext) {
throw new IllegalStateException("Cannot remove a watcher from within another");
}
mWatchers.remove(name);
}
/**
* This method forces all registered watchers to run.
* See {@link #registerWatcher(String, UiWatcher)}
* @since API Level 16
*/
public void runWatchers() {
Tracer.trace();
if (mInWatcherContext) {
return;
}
for (String watcherName : mWatchers.keySet()) {
UiWatcher watcher = mWatchers.get(watcherName);
if (watcher != null) {
try {
mInWatcherContext = true;
if (watcher.checkForCondition()) {
setWatcherTriggered(watcherName);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exceuting watcher: " + watcherName, e);
} finally {
mInWatcherContext = false;
}
}
}
}
/**
* Resets a {@link UiWatcher} that has been triggered.
* If a UiWatcher runs and its {@link UiWatcher#checkForCondition()} call
* returned <code>true</code>, then the UiWatcher is considered triggered.
* See {@link #registerWatcher(String, UiWatcher)}
* @since API Level 16
*/
public void resetWatcherTriggers() {
Tracer.trace();
mWatchersTriggers.clear();
}
/**
* Checks if a specific registered {@link UiWatcher} has triggered.
* See {@link #registerWatcher(String, UiWatcher)}. If a UiWatcher runs and its
* {@link UiWatcher#checkForCondition()} call returned <code>true</code>, then
* the UiWatcher is considered triggered. This is helpful if a watcher is detecting errors
* from ANR or crash dialogs and the test needs to know if a UiWatcher has been triggered.
*
* @param watcherName
* @return true if triggered else false
* @since API Level 16
*/
public boolean hasWatcherTriggered(String watcherName) {
Tracer.trace(watcherName);
return mWatchersTriggers.contains(watcherName);
}
/**
* Checks if any registered {@link UiWatcher} have triggered.
*
* See {@link #registerWatcher(String, UiWatcher)}
* See {@link #hasWatcherTriggered(String)}
* @since API Level 16
*/
public boolean hasAnyWatcherTriggered() {
Tracer.trace();
return mWatchersTriggers.size() > 0;
}
/**
* Used internally by this class to set a {@link UiWatcher} state as triggered.
* @param watcherName
*/
private void setWatcherTriggered(String watcherName) {
Tracer.trace(watcherName);
if (!hasWatcherTriggered(watcherName)) {
mWatchersTriggers.add(watcherName);
}
}
/**
* Check if the device is in its natural orientation. This is determined by checking if the
* orientation is at 0 or 180 degrees.
* @return true if it is in natural orientation
* @since API Level 17
*/
public boolean isNaturalOrientation() {
Tracer.trace();
waitForIdle();
int ret = getAutomatorBridge().getRotation();
return ret == UiAutomation.ROTATION_FREEZE_0 ||
ret == UiAutomation.ROTATION_FREEZE_180;
}
/**
* Returns the current rotation of the display, as defined in {@link Surface}
* @since API Level 17
*/
public int getDisplayRotation() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getRotation();
}
/**
* Disables the sensors and freezes the device rotation at its
* current rotation state.
* @throws RemoteException
* @since API Level 16
*/
public void freezeRotation() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().freezeRotation();
}
/**
* Re-enables the sensors and un-freezes the device rotation allowing its contents
* to rotate with the device physical rotation. During a test execution, it is best to
* keep the device frozen in a specific orientation until the test case execution has completed.
* @throws RemoteException
*/
public void unfreezeRotation() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().unfreezeRotation();
}
/**
* Simulates orienting the device to the left and also freezes rotation
* by disabling the sensors.
*
* If you want to un-freeze the rotation and re-enable the sensors
* see {@link #unfreezeRotation()}.
* @throws RemoteException
* @since API Level 17
*/
public void setOrientationLeft() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().setRotationLeft();
waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
}
/**
* Simulates orienting the device to the right and also freezes rotation
* by disabling the sensors.
*
* If you want to un-freeze the rotation and re-enable the sensors
* see {@link #unfreezeRotation()}.
* @throws RemoteException
* @since API Level 17
*/
public void setOrientationRight() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().setRotationRight();
waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
}
/**
* Simulates orienting the device into its natural orientation and also freezes rotation
* by disabling the sensors.
*
* If you want to un-freeze the rotation and re-enable the sensors
* see {@link #unfreezeRotation()}.
* @throws RemoteException
* @since API Level 17
*/
public void setOrientationNatural() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().setRotationNatural();
waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
}
/**
* This method simulates pressing the power button if the screen is OFF else
* it does nothing if the screen is already ON.
*
* If the screen was OFF and it just got turned ON, this method will insert a 500ms delay
* to allow the device time to wake up and accept input.
* @throws RemoteException
* @since API Level 16
*/
public void wakeUp() throws RemoteException {
Tracer.trace();
if(getAutomatorBridge().getInteractionController().wakeDevice()) {
// sync delay to allow the window manager to start accepting input
// after the device is awakened.
SystemClock.sleep(500);
}
}
/**
* Checks the power manager if the screen is ON.
*
* @return true if the screen is ON else false
* @throws RemoteException
* @since API Level 16
*/
public boolean isScreenOn() throws RemoteException {
Tracer.trace();
return getAutomatorBridge().getInteractionController().isScreenOn();
}
/**
* This method simply presses the power button if the screen is ON else
* it does nothing if the screen is already OFF.
*
* @throws RemoteException
* @since API Level 16
*/
public void sleep() throws RemoteException {
Tracer.trace();
getAutomatorBridge().getInteractionController().sleepDevice();
}
/**
* Helper method used for debugging to dump the current window's layout hierarchy.
* The file root location is /data/local/tmp
*
* @param fileName
* @since API Level 16
*/
public void dumpWindowHierarchy(String fileName) {
Tracer.trace(fileName);
AccessibilityNodeInfo root =
getAutomatorBridge().getQueryController().getAccessibilityRootNode();
if(root != null) {
Display display = getAutomatorBridge().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
display.getRotation(), size.x, size.y);
}
}
/**
* Waits for a window content update event to occur.
*
* If a package name for the window is specified, but the current window
* does not have the same package name, the function returns immediately.
*
* @param packageName the specified window package name (can be <code>null</code>).
* If <code>null</code>, a window update from any front-end window will end the wait
* @param timeout the timeout for the wait
*
* @return true if a window update occurred, false if timeout has elapsed or if the current
* window does not have the specified package name
* @since API Level 16
*/
public boolean waitForWindowUpdate(final String packageName, long timeout) {
Tracer.trace(packageName, timeout);
if (packageName != null) {
if (!packageName.equals(getCurrentPackageName())) {
return false;
}
}
Runnable emptyRunnable = new Runnable() {
@Override
public void run() {
}
};
AccessibilityEventFilter checkWindowUpdate = new AccessibilityEventFilter() {
@Override
public boolean accept(AccessibilityEvent t) {
if (t.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
return packageName == null || packageName.equals(t.getPackageName());
}
return false;
}
};
try {
getAutomatorBridge().executeCommandAndWaitForAccessibilityEvent(
emptyRunnable, checkWindowUpdate, timeout);
} catch (TimeoutException e) {
return false;
} catch (Exception e) {
Log.e(LOG_TAG, "waitForWindowUpdate: general exception from bridge", e);
return false;
}
return true;
}
/**
* Take a screenshot of current window and store it as PNG
*
* Default scale of 1.0f (original size) and 90% quality is used
* The screenshot is adjusted per screen rotation
*
* @param storePath where the PNG should be written to
* @return true if screen shot is created successfully, false otherwise
* @since API Level 17
*/
public boolean takeScreenshot(File storePath) {
Tracer.trace(storePath);
return takeScreenshot(storePath, 1.0f, 90);
}
/**
* Take a screenshot of current window and store it as PNG
*
* The screenshot is adjusted per screen rotation
*
* @param storePath where the PNG should be written to
* @param scale scale the screenshot down if needed; 1.0f for original size
* @param quality quality of the PNG compression; range: 0-100
* @return true if screen shot is created successfully, false otherwise
* @since API Level 17
*/
public boolean takeScreenshot(File storePath, float scale, int quality) {
Tracer.trace(storePath, scale, quality);
return getAutomatorBridge().takeScreenshot(storePath, quality);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
/*
* 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 com.android.uiautomator.core;
/**
* Generated in test runs when a {@link UiSelector} selector could not be matched
* to any UI element displayed.
* @since API Level 16
*/
public class UiObjectNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
/**
* @since API Level 16
**/
public UiObjectNotFoundException(String msg) {
super(msg);
}
/**
* @since API Level 16
**/
public UiObjectNotFoundException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
/**
* @since API Level 16
**/
public UiObjectNotFoundException(Throwable throwable) {
super(throwable);
}
}

View File

@ -0,0 +1,665 @@
/*
* 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 com.android.uiautomator.core;
import android.graphics.Rect;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* UiScrollable is a {@link UiCollection} and provides support for searching
* for items in scrollable layout elements. This class can be used with
* horizontally or vertically scrollable controls.
* @since API Level 16
*/
public class UiScrollable extends UiCollection {
private static final String LOG_TAG = UiScrollable.class.getSimpleName();
// More steps slows the swipe and prevents contents from being flung too far
private static final int SCROLL_STEPS = 55;
private static final int FLING_STEPS = 5;
// Restrict a swipe's starting and ending points inside a 10% margin of the target
private static final double DEFAULT_SWIPE_DEADZONE_PCT = 0.1;
// Limits the number of swipes/scrolls performed during a search
private static int mMaxSearchSwipes = 30;
// Used in ScrollForward() and ScrollBackward() to determine swipe direction
private boolean mIsVerticalList = true;
private double mSwipeDeadZonePercentage = DEFAULT_SWIPE_DEADZONE_PCT;
/**
* Constructor.
*
* @param container a {@link UiSelector} selector to identify the scrollable
* layout element.
* @since API Level 16
*/
public UiScrollable(UiSelector container) {
// wrap the container selector with container so that QueryController can handle
// this type of enumeration search accordingly
super(container);
}
/**
* Set the direction of swipes to be vertical when performing scroll actions.
* @return reference to itself
* @since API Level 16
*/
public UiScrollable setAsVerticalList() {
Tracer.trace();
mIsVerticalList = true;
return this;
}
/**
* Set the direction of swipes to be horizontal when performing scroll actions.
* @return reference to itself
* @since API Level 16
*/
public UiScrollable setAsHorizontalList() {
Tracer.trace();
mIsVerticalList = false;
return this;
}
/**
* Used privately when performing swipe searches to decide if an element has become
* visible or not.
*
* @param selector
* @return true if found else false
* @since API Level 16
*/
protected boolean exists(UiSelector selector) {
if(getQueryController().findAccessibilityNodeInfo(selector) != null) {
return true;
}
return false;
}
/**
* Searches for a child element in the present scrollable container.
* The search first looks for a child element that matches the selector
* you provided, then looks for the content-description in its children elements.
* If both search conditions are fulfilled, the method returns a {@ link UiObject}
* representing the element matching the selector (not the child element in its
* subhierarchy containing the content-description). By default, this method performs a
* scroll search.
* See {@link #getChildByDescription(UiSelector, String, boolean)}
*
* @param childPattern {@link UiSelector} for a child in a scollable layout element
* @param text Content-description to find in the children of
* the <code>childPattern</code> match
* @return {@link UiObject} representing the child element that matches the search conditions
* @throws UiObjectNotFoundException
* @since API Level 16
*/
@Override
public UiObject getChildByDescription(UiSelector childPattern, String text)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, text);
return getChildByDescription(childPattern, text, true);
}
/**
* Searches for a child element in the present scrollable container.
* The search first looks for a child element that matches the selector
* you provided, then looks for the content-description in its children elements.
* If both search conditions are fulfilled, the method returns a {@ link UiObject}
* representing the element matching the selector (not the child element in its
* subhierarchy containing the content-description).
*
* @param childPattern {@link UiSelector} for a child in a scollable layout element
* @param text Content-description to find in the children of
* the <code>childPattern</code> match (may be a partial match)
* @param allowScrollSearch set to true if scrolling is allowed
* @return {@link UiObject} representing the child element that matches the search conditions
* @throws UiObjectNotFoundException
* @since API Level 16
*/
public UiObject getChildByDescription(UiSelector childPattern, String text,
boolean allowScrollSearch) throws UiObjectNotFoundException {
Tracer.trace(childPattern, text, allowScrollSearch);
if (text != null) {
if (allowScrollSearch) {
scrollIntoView(new UiSelector().descriptionContains(text));
}
return super.getChildByDescription(childPattern, text);
}
throw new UiObjectNotFoundException("for description= \"" + text + "\"");
}
/**
* Searches for a child element in the present scrollable container that
* matches the selector you provided. The search is performed without
* scrolling and only on visible elements.
*
* @param childPattern {@link UiSelector} for a child in a scollable layout element
* @param instance int number representing the occurance of
* a <code>childPattern</code> match
* @return {@link UiObject} representing the child element that matches the search conditions
* @since API Level 16
*/
@Override
public UiObject getChildByInstance(UiSelector childPattern, int instance)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, instance);
UiSelector patternSelector = UiSelector.patternBuilder(getSelector(),
UiSelector.patternBuilder(childPattern).instance(instance));
return new UiObject(patternSelector);
}
/**
* Searches for a child element in the present scrollable
* container. The search first looks for a child element that matches the
* selector you provided, then looks for the text in its children elements.
* If both search conditions are fulfilled, the method returns a {@ link UiObject}
* representing the element matching the selector (not the child element in its
* subhierarchy containing the text). By default, this method performs a
* scroll search.
* See {@link #getChildByText(UiSelector, String, boolean)}
*
* @param childPattern {@link UiSelector} selector for a child in a scrollable layout element
* @param text String to find in the children of the <code>childPattern</code> match
* @return {@link UiObject} representing the child element that matches the search conditions
* @throws UiObjectNotFoundException
* @since API Level 16
*/
@Override
public UiObject getChildByText(UiSelector childPattern, String text)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, text);
return getChildByText(childPattern, text, true);
}
/**
* Searches for a child element in the present scrollable container. The
* search first looks for a child element that matches the
* selector you provided, then looks for the text in its children elements.
* If both search conditions are fulfilled, the method returns a {@ link UiObject}
* representing the element matching the selector (not the child element in its
* subhierarchy containing the text).
*
* @param childPattern {@link UiSelector} selector for a child in a scrollable layout element
* @param text String to find in the children of the <code>childPattern</code> match
* @param allowScrollSearch set to true if scrolling is allowed
* @return {@link UiObject} representing the child element that matches the search conditions
* @throws UiObjectNotFoundException
* @since API Level 16
*/
public UiObject getChildByText(UiSelector childPattern, String text, boolean allowScrollSearch)
throws UiObjectNotFoundException {
Tracer.trace(childPattern, text, allowScrollSearch);
if (text != null) {
if (allowScrollSearch) {
scrollIntoView(new UiSelector().text(text));
}
return super.getChildByText(childPattern, text);
}
throw new UiObjectNotFoundException("for text= \"" + text + "\"");
}
/**
* Performs a forward scroll action on the scrollable layout element until
* the content-description is found, or until swipe attempts have been exhausted.
* See {@link #setMaxSearchSwipes(int)}
*
* @param text content-description to find within the contents of this scrollable layout element.
* @return true if item is found; else, false
* @since API Level 16
*/
public boolean scrollDescriptionIntoView(String text) throws UiObjectNotFoundException {
Tracer.trace(text);
return scrollIntoView(new UiSelector().description(text));
}
/**
* Perform a forward scroll action to move through the scrollable layout element until
* a visible item that matches the {@link UiObject} is found.
*
* @param obj {@link UiObject}
* @return true if the item was found and now is in view else false
* @since API Level 16
*/
public boolean scrollIntoView(UiObject obj) throws UiObjectNotFoundException {
Tracer.trace(obj.getSelector());
return scrollIntoView(obj.getSelector());
}
/**
* Perform a scroll forward action to move through the scrollable layout
* element until a visible item that matches the selector is found.
*
* See {@link #scrollDescriptionIntoView(String)} and {@link #scrollTextIntoView(String)}.
*
* @param selector {@link UiSelector} selector
* @return true if the item was found and now is in view; else, false
* @since API Level 16
*/
public boolean scrollIntoView(UiSelector selector) throws UiObjectNotFoundException {
Tracer.trace(selector);
// if we happen to be on top of the text we want then return here
UiSelector childSelector = getSelector().childSelector(selector);
if (exists(childSelector)) {
return (true);
} else {
// we will need to reset the search from the beginning to start search
scrollToBeginning(mMaxSearchSwipes);
if (exists(childSelector)) {
return (true);
}
for (int x = 0; x < mMaxSearchSwipes; x++) {
boolean scrolled = scrollForward();
if(exists(childSelector)) {
return true;
}
if (!scrolled) {
return false;
}
}
}
return false;
}
/**
* Scrolls forward until the UiObject is fully visible in the scrollable container.
* Use this method to make sure that the child item's edges are not offscreen.
*
* @param childObject {@link UiObject} representing the child element
* @return true if the child element is already fully visible, or
* if the method scrolled successfully until the child became fully visible;
* otherwise, false if the attempt to scroll failed.
* @throws UiObjectNotFoundException
* @hide
*/
public boolean ensureFullyVisible(UiObject childObject) throws UiObjectNotFoundException {
Rect actual = childObject.getBounds();
Rect visible = childObject.getVisibleBounds();
if (visible.width() * visible.height() == actual.width() * actual.height()) {
// area match, item fully visible
return true;
}
boolean shouldSwipeForward = false;
if (mIsVerticalList) {
// if list is vertical, matching top edge implies obscured bottom edge
// so we need to scroll list forward
shouldSwipeForward = actual.top == visible.top;
} else {
// if list is horizontal, matching left edge implies obscured right edge,
// so we need to scroll list forward
shouldSwipeForward = actual.left == visible.left;
}
if (mIsVerticalList) {
if (shouldSwipeForward) {
return swipeUp(10);
} else {
return swipeDown(10);
}
} else {
if (shouldSwipeForward) {
return swipeLeft(10);
} else {
return swipeRight(10);
}
}
}
/**
* Performs a forward scroll action on the scrollable layout element until
* the text you provided is visible, or until swipe attempts have been exhausted.
* See {@link #setMaxSearchSwipes(int)}
*
* @param text test to look for
* @return true if item is found; else, false
* @since API Level 16
*/
public boolean scrollTextIntoView(String text) throws UiObjectNotFoundException {
Tracer.trace(text);
return scrollIntoView(new UiSelector().text(text));
}
/**
* Sets the maximum number of scrolls allowed when performing a
* scroll action in search of a child element.
* See {@link #getChildByDescription(UiSelector, String)} and
* {@link #getChildByText(UiSelector, String)}.
*
* @param swipes the number of search swipes to perform until giving up
* @return reference to itself
* @since API Level 16
*/
public UiScrollable setMaxSearchSwipes(int swipes) {
Tracer.trace(swipes);
mMaxSearchSwipes = swipes;
return this;
}
/**
* Gets the maximum number of scrolls allowed when performing a
* scroll action in search of a child element.
* See {@link #getChildByDescription(UiSelector, String)} and
* {@link #getChildByText(UiSelector, String)}.
*
* @return max the number of search swipes to perform until giving up
* @since API Level 16
*/
public int getMaxSearchSwipes() {
Tracer.trace();
return mMaxSearchSwipes;
}
/**
* Performs a forward fling with the default number of fling steps (5).
* If the swipe direction is set to vertical, then the swipes will be
* performed from bottom to top. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* right to left. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @return true if scrolled, false if can't scroll anymore
* @since API Level 16
*/
public boolean flingForward() throws UiObjectNotFoundException {
Tracer.trace();
return scrollForward(FLING_STEPS);
}
/**
* Performs a forward scroll with the default number of scroll steps (55).
* If the swipe direction is set to vertical,
* then the swipes will be performed from bottom to top. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* right to left. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @return true if scrolled, false if can't scroll anymore
* @since API Level 16
*/
public boolean scrollForward() throws UiObjectNotFoundException {
Tracer.trace();
return scrollForward(SCROLL_STEPS);
}
/**
* Performs a forward scroll. If the swipe direction is set to vertical,
* then the swipes will be performed from bottom to top. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* right to left. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @param steps number of steps. Use this to control the speed of the scroll action
* @return true if scrolled, false if can't scroll anymore
* @since API Level 16
*/
public boolean scrollForward(int steps) throws UiObjectNotFoundException {
Tracer.trace(steps);
Log.d(LOG_TAG, "scrollForward() on selector = " + getSelector());
AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
if(node == null) {
throw new UiObjectNotFoundException(getSelector().toString());
}
Rect rect = new Rect();
node.getBoundsInScreen(rect);
int downX = 0;
int downY = 0;
int upX = 0;
int upY = 0;
// scrolling is by default assumed vertically unless the object is explicitly
// set otherwise by setAsHorizontalContainer()
if(mIsVerticalList) {
int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
// scroll vertically: swipe down -> up
downX = rect.centerX();
downY = rect.bottom - swipeAreaAdjust;
upX = rect.centerX();
upY = rect.top + swipeAreaAdjust;
} else {
int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
// scroll horizontally: swipe right -> left
// TODO: Assuming device is not in right to left language
downX = rect.right - swipeAreaAdjust;
downY = rect.centerY();
upX = rect.left + swipeAreaAdjust;
upY = rect.centerY();
}
return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
}
/**
* Performs a backwards fling action with the default number of fling
* steps (5). If the swipe direction is set to vertical,
* then the swipe will be performed from top to bottom. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* left to right. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @return true if scrolled, and false if can't scroll anymore
* @since API Level 16
*/
public boolean flingBackward() throws UiObjectNotFoundException {
Tracer.trace();
return scrollBackward(FLING_STEPS);
}
/**
* Performs a backward scroll with the default number of scroll steps (55).
* If the swipe direction is set to vertical,
* then the swipes will be performed from top to bottom. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* left to right. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @return true if scrolled, and false if can't scroll anymore
* @since API Level 16
*/
public boolean scrollBackward() throws UiObjectNotFoundException {
Tracer.trace();
return scrollBackward(SCROLL_STEPS);
}
/**
* Performs a backward scroll. If the swipe direction is set to vertical,
* then the swipes will be performed from top to bottom. If the swipe
* direction is set to horizontal, then the swipes will be performed from
* left to right. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @param steps number of steps. Use this to control the speed of the scroll action.
* @return true if scrolled, false if can't scroll anymore
* @since API Level 16
*/
public boolean scrollBackward(int steps) throws UiObjectNotFoundException {
Tracer.trace(steps);
Log.d(LOG_TAG, "scrollBackward() on selector = " + getSelector());
AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
if (node == null) {
throw new UiObjectNotFoundException(getSelector().toString());
}
Rect rect = new Rect();
node.getBoundsInScreen(rect);
int downX = 0;
int downY = 0;
int upX = 0;
int upY = 0;
// scrolling is by default assumed vertically unless the object is explicitly
// set otherwise by setAsHorizontalContainer()
if(mIsVerticalList) {
int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
Log.d(LOG_TAG, "scrollToBegining() using vertical scroll");
// scroll vertically: swipe up -> down
downX = rect.centerX();
downY = rect.top + swipeAreaAdjust;
upX = rect.centerX();
upY = rect.bottom - swipeAreaAdjust;
} else {
int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
Log.d(LOG_TAG, "scrollToBegining() using hotizontal scroll");
// scroll horizontally: swipe left -> right
// TODO: Assuming device is not in right to left language
downX = rect.left + swipeAreaAdjust;
downY = rect.centerY();
upX = rect.right - swipeAreaAdjust;
upY = rect.centerY();
}
return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
}
/**
* Scrolls to the beginning of a scrollable layout element. The beginning
* can be at the top-most edge in the case of vertical controls, or the
* left-most edge for horizontal controls. Make sure to take into account
* devices configured with right-to-left languages like Arabic and Hebrew.
*
* @param steps use steps to control the speed, so that it may be a scroll, or fling
* @return true on scrolled else false
* @since API Level 16
*/
public boolean scrollToBeginning(int maxSwipes, int steps) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes, steps);
Log.d(LOG_TAG, "scrollToBeginning() on selector = " + getSelector());
// protect against potential hanging and return after preset attempts
for(int x = 0; x < maxSwipes; x++) {
if(!scrollBackward(steps)) {
break;
}
}
return true;
}
/**
* Scrolls to the beginning of a scrollable layout element. The beginning
* can be at the top-most edge in the case of vertical controls, or the
* left-most edge for horizontal controls. Make sure to take into account
* devices configured with right-to-left languages like Arabic and Hebrew.
*
* @param maxSwipes
* @return true on scrolled else false
* @since API Level 16
*/
public boolean scrollToBeginning(int maxSwipes) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes);
return scrollToBeginning(maxSwipes, SCROLL_STEPS);
}
/**
* Performs a fling gesture to reach the beginning of a scrollable layout element.
* The beginning can be at the top-most edge in the case of vertical controls, or
* the left-most edge for horizontal controls. Make sure to take into
* account devices configured with right-to-left languages like Arabic and Hebrew.
*
* @param maxSwipes
* @return true on scrolled else false
* @since API Level 16
*/
public boolean flingToBeginning(int maxSwipes) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes);
return scrollToBeginning(maxSwipes, FLING_STEPS);
}
/**
* Scrolls to the end of a scrollable layout element. The end can be at the
* bottom-most edge in the case of vertical controls, or the right-most edge for
* horizontal controls. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @param steps use steps to control the speed, so that it may be a scroll, or fling
* @return true on scrolled else false
* @since API Level 16
*/
public boolean scrollToEnd(int maxSwipes, int steps) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes, steps);
// protect against potential hanging and return after preset attempts
for(int x = 0; x < maxSwipes; x++) {
if(!scrollForward(steps)) {
break;
}
}
return true;
}
/**
* Scrolls to the end of a scrollable layout element. The end can be at the
* bottom-most edge in the case of vertical controls, or the right-most edge for
* horizontal controls. Make sure to take into account devices configured with
* right-to-left languages like Arabic and Hebrew.
*
* @param maxSwipes
* @return true on scrolled, else false
* @since API Level 16
*/
public boolean scrollToEnd(int maxSwipes) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes);
return scrollToEnd(maxSwipes, SCROLL_STEPS);
}
/**
* Performs a fling gesture to reach the end of a scrollable layout element.
* The end can be at the bottom-most edge in the case of vertical controls, or
* the right-most edge for horizontal controls. Make sure to take into
* account devices configured with right-to-left languages like Arabic and Hebrew.
*
* @param maxSwipes
* @return true on scrolled, else false
* @since API Level 16
*/
public boolean flingToEnd(int maxSwipes) throws UiObjectNotFoundException {
Tracer.trace(maxSwipes);
return scrollToEnd(maxSwipes, FLING_STEPS);
}
/**
* Returns the percentage of a widget's size that's considered as a no-touch
* zone when swiping. The no-touch zone is set as a percentage of a widget's total
* width or height, denoting a margin around the swipable area of the widget.
* Swipes must start and end inside this margin. This is important when the
* widget being swiped may not respond to the swipe if started at a point
* too near to the edge. The default is 10% from either edge.
*
* @return a value between 0 and 1
* @since API Level 16
*/
public double getSwipeDeadZonePercentage() {
Tracer.trace();
return mSwipeDeadZonePercentage;
}
/**
* Sets the percentage of a widget's size that's considered as no-touch
* zone when swiping.
* The no-touch zone is set as percentage of a widget's total width or height,
* denoting a margin around the swipable area of the widget. Swipes must
* always start and end inside this margin. This is important when the
* widget being swiped may not respond to the swipe if started at a point
* too near to the edge. The default is 10% from either edge.
*
* @param swipeDeadZonePercentage is a value between 0 and 1
* @return reference to itself
* @since API Level 16
*/
public UiScrollable setSwipeDeadZonePercentage(double swipeDeadZonePercentage) {
Tracer.trace(swipeDeadZonePercentage);
mSwipeDeadZonePercentage = swipeDeadZonePercentage;
return this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
/*
* 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 com.android.uiautomator.core;
/**
* See {@link UiDevice#registerWatcher(String, UiWatcher)} on how to register a
* a condition watcher to be called by the automation library. The automation library will
* invoke checkForCondition() only when a regular API call is in retry mode because it is unable
* to locate its selector yet. Only during this time, the watchers are invoked to check if there is
* something else unexpected on the screen.
* @since API Level 16
*/
public interface UiWatcher {
/**
* Custom handler that is automatically called when the testing framework is unable to
* find a match using the {@link UiSelector}
*
* When the framework is in the process of matching a {@link UiSelector} and it
* is unable to match any widget based on the specified criteria in the selector,
* the framework will perform retries for a predetermined time, waiting for the display
* to update and show the desired widget. While the framework is in this state, it will call
* registered watchers' checkForCondition(). This gives the registered watchers a chance
* to take a look at the display and see if there is a recognized condition that can be
* handled and in doing so allowing the current test to continue.
*
* An example usage would be to look for dialogs popped due to other background
* processes requesting user attention and have nothing to do with the application
* currently under test.
*
* @return true to indicate a matched condition or false for nothing was matched
* @since API Level 16
*/
public boolean checkForCondition();
}

View File

@ -0,0 +1,122 @@
/*
* 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 com.android.uiautomator.core;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IActivityManager.ContentProviderHolder;
import android.app.UiAutomation;
import android.content.Context;
import android.content.IContentProvider;
import android.database.Cursor;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.IWindowManager;
/**
* @hide
*/
public class ShellUiAutomatorBridge extends UiAutomatorBridge {
private static final String LOG_TAG = ShellUiAutomatorBridge.class.getSimpleName();
public ShellUiAutomatorBridge(UiAutomation uiAutomation) {
super(uiAutomation);
}
public Display getDefaultDisplay() {
return DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
}
public long getSystemLongPressTime() {
// Read the long press timeout setting.
long longPressTimeout = 0;
try {
IContentProvider provider = null;
Cursor cursor = null;
IActivityManager activityManager = ActivityManagerNative.getDefault();
String providerName = Settings.Secure.CONTENT_URI.getAuthority();
IBinder token = new Binder();
try {
ContentProviderHolder holder = activityManager.getContentProviderExternal(
providerName, UserHandle.USER_OWNER, token);
if (holder == null) {
throw new IllegalStateException("Could not find provider: " + providerName);
}
provider = holder.provider;
cursor = provider.query(null, Settings.Secure.CONTENT_URI,
new String[] {
Settings.Secure.VALUE
}, "name=?",
new String[] {
Settings.Secure.LONG_PRESS_TIMEOUT
}, null, null);
if (cursor.moveToFirst()) {
longPressTimeout = cursor.getInt(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
if (provider != null) {
activityManager.removeContentProviderExternal(providerName, token);
}
}
} catch (RemoteException e) {
String message = "Error reading long press timeout setting.";
Log.e(LOG_TAG, message, e);
throw new RuntimeException(message, e);
}
return longPressTimeout;
}
@Override
public int getRotation() {
IWindowManager wm =
IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
int ret = -1;
try {
ret = wm.getRotation();
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error getting screen rotation", e);
throw new RuntimeException(e);
}
return ret;
}
@Override
public boolean isScreenOn() {
IPowerManager pm =
IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
boolean ret = false;
try {
ret = pm.isInteractive();
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error getting screen status", e);
throw new RuntimeException(e);
}
return ret;
}
}

View File

@ -0,0 +1,127 @@
package com.android.uiautomator.core;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.UiAutomation;
import android.app.UiAutomationConnection;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.RemoteException;
/**
* @hide
*/
public class UiAutomationShellWrapper {
private static final String HANDLER_THREAD_NAME = "UiAutomatorHandlerThread";
private final HandlerThread mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
private UiAutomation mUiAutomation;
public void connect() {
if (mHandlerThread.isAlive()) {
throw new IllegalStateException("Already connected!");
}
mHandlerThread.start();
mUiAutomation = new UiAutomation(mHandlerThread.getLooper(),
new UiAutomationConnection());
mUiAutomation.connect();
}
/**
* Enable or disable monkey test mode.
*
* Setting test as "monkey" indicates to some applications that a test framework is
* running as a "monkey" type. Such applications may choose not to perform actions that
* do submits so to avoid allowing monkey tests from doing harm or performing annoying
* actions such as dialing 911 or posting messages to public forums, etc.
*
* @param isSet True to set as monkey test. False to set as regular functional test (default).
* @see {@link ActivityManager#isUserAMonkey()}
*/
public void setRunAsMonkey(boolean isSet) {
IActivityManager am = ActivityManagerNative.getDefault();
if (am == null) {
throw new RuntimeException("Can't manage monkey status; is the system running?");
}
try {
if (isSet) {
am.setActivityController(new DummyActivityController());
} else {
am.setActivityController(null);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public void disconnect() {
if (!mHandlerThread.isAlive()) {
throw new IllegalStateException("Already disconnected!");
}
mUiAutomation.disconnect();
mHandlerThread.quit();
}
public UiAutomation getUiAutomation() {
return mUiAutomation;
}
public void setCompressedLayoutHierarchy(boolean compressed) {
AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
if (compressed)
info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
else
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
mUiAutomation.setServiceInfo(info);
}
/**
* Dummy, no interference, activity controller.
*/
private class DummyActivityController extends IActivityController.Stub {
@Override
public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
/* do nothing and let activity proceed normally */
return true;
}
@Override
public boolean activityResuming(String pkg) throws RemoteException {
/* do nothing and let activity proceed normally */
return true;
}
@Override
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
long timeMillis, String stackTrace) throws RemoteException {
/* do nothing and let activity proceed normally */
return true;
}
@Override
public int appEarlyNotResponding(String processName, int pid, String annotation)
throws RemoteException {
/* do nothing and let activity proceed normally */
return 0;
}
@Override
public int appNotResponding(String processName, int pid, String processStats)
throws RemoteException {
/* do nothing and let activity proceed normally */
return 0;
}
@Override
public int systemNotResponding(String message)
throws RemoteException {
/* do nothing and let system proceed normally */
return 0;
}
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.os.Bundle;
/**
* Provides auxiliary support for running test cases
*
* @since API Level 16
*/
public interface IAutomationSupport {
/**
* Allows the running test cases to send out interim status
*
* @param resultCode
* @param status status report, consisting of key value pairs
* @since API Level 16
*/
public void sendStatus(int resultCode, Bundle status);
}

View File

@ -0,0 +1,152 @@
/*
* 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 com.android.uiautomator.testrunner;
import junit.framework.TestCase;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A convenient class that encapsulates functions for adding test classes
*
* @hide
*/
public class TestCaseCollector {
private ClassLoader mClassLoader;
private List<TestCase> mTestCases;
private TestCaseFilter mFilter;
public TestCaseCollector(ClassLoader classLoader, TestCaseFilter filter) {
mClassLoader = classLoader;
mTestCases = new ArrayList<TestCase>();
mFilter = filter;
}
/**
* Adds classes to test by providing a list of class names in string
*
* The class name may be in "<class name>#<method name>" format
*
* @param classNames class must be subclass of {@link UiAutomatorTestCase}
* @throws ClassNotFoundException
*/
public void addTestClasses(List<String> classNames) throws ClassNotFoundException {
for (String className : classNames) {
addTestClass(className);
}
}
/**
* Adds class to test by providing class name in string.
*
* The class name may be in "<class name>#<method name>" format
*
* @param className classes must be subclass of {@link UiAutomatorTestCase}
* @throws ClassNotFoundException
*/
public void addTestClass(String className) throws ClassNotFoundException {
int hashPos = className.indexOf('#');
String methodName = null;
if (hashPos != -1) {
methodName = className.substring(hashPos + 1);
className = className.substring(0, hashPos);
}
addTestClass(className, methodName);
}
/**
* Adds class to test by providing class name and method name in separate strings
*
* @param className class must be subclass of {@link UiAutomatorTestCase}
* @param methodName may be null, in which case all "public void testNNN(void)" functions
* will be added
* @throws ClassNotFoundException
*/
public void addTestClass(String className, String methodName) throws ClassNotFoundException {
Class<?> clazz = mClassLoader.loadClass(className);
if (methodName != null) {
addSingleTestMethod(clazz, methodName);
} else {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (mFilter.accept(method)) {
addSingleTestMethod(clazz, method.getName());
}
}
}
}
/**
* Gets the list of added test cases so far
* @return a list of {@link TestCase}
*/
public List<TestCase> getTestCases() {
return Collections.unmodifiableList(mTestCases);
}
protected void addSingleTestMethod(Class<?> clazz, String method) {
if (!(mFilter.accept(clazz))) {
throw new RuntimeException("Test class must be derived from UiAutomatorTestCase");
}
try {
TestCase testCase = (TestCase) clazz.newInstance();
testCase.setName(method);
mTestCases.add(testCase);
} catch (InstantiationException e) {
mTestCases.add(error(clazz, "InstantiationException: could not instantiate " +
"test class. Class: " + clazz.getName()));
} catch (IllegalAccessException e) {
mTestCases.add(error(clazz, "IllegalAccessException: could not instantiate " +
"test class. Class: " + clazz.getName()));
}
}
private UiAutomatorTestCase error(Class<?> clazz, final String message) {
UiAutomatorTestCase warning = new UiAutomatorTestCase() {
protected void runTest() {
fail(message);
}
};
warning.setName(clazz.getName());
return warning;
}
/**
* Determine if a class and its method should be accepted into test suite
*
*/
public interface TestCaseFilter {
/**
* Determine that based on the method signature, if it can be accepted
* @param method
*/
public boolean accept(Method method);
/**
* Determine that based on the class type, if it can be accepted
* @param clazz
* @return
*/
public boolean accept(Class<?> clazz);
}
}

View File

@ -0,0 +1,147 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.view.IInputMethodManager;
import com.android.uiautomator.core.UiDevice;
import junit.framework.TestCase;
import java.util.List;
/**
* UI automation test should extend this class. This class provides access
* to the following:
* {@link UiDevice} instance
* {@link Bundle} for command line parameters.
* @since API Level 16
*/
public class UiAutomatorTestCase extends TestCase {
private static final String DISABLE_IME = "disable_ime";
private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
private UiDevice mUiDevice;
private Bundle mParams;
private IAutomationSupport mAutomationSupport;
private boolean mShouldDisableIme = false;
@Override
protected void setUp() throws Exception {
super.setUp();
mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
if (mShouldDisableIme) {
setDummyIme();
}
}
@Override
protected void tearDown() throws Exception {
if (mShouldDisableIme) {
restoreActiveIme();
}
super.tearDown();
}
/**
* Get current instance of {@link UiDevice}. Works similar to calling the static
* {@link UiDevice#getInstance()} from anywhere in the test classes.
* @since API Level 16
*/
public UiDevice getUiDevice() {
return mUiDevice;
}
/**
* Get command line parameters. On the command line when passing <code>-e key value</code>
* pairs, the {@link Bundle} will have the key value pairs conveniently available to the
* tests.
* @since API Level 16
*/
public Bundle getParams() {
return mParams;
}
/**
* Provides support for running tests to report interim status
*
* @return IAutomationSupport
* @since API Level 16
*/
public IAutomationSupport getAutomationSupport() {
return mAutomationSupport;
}
/**
* package private
* @param uiDevice
*/
void setUiDevice(UiDevice uiDevice) {
mUiDevice = uiDevice;
}
/**
* package private
* @param params
*/
void setParams(Bundle params) {
mParams = params;
}
void setAutomationSupport(IAutomationSupport automationSupport) {
mAutomationSupport = automationSupport;
}
/**
* Calls {@link SystemClock#sleep(long)} to sleep
* @param ms is in milliseconds.
* @since API Level 16
*/
public void sleep(long ms) {
SystemClock.sleep(ms);
}
private void setDummyIme() throws RemoteException {
IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
.getService(Context.INPUT_METHOD_SERVICE));
List<InputMethodInfo> infos = im.getInputMethodList();
String id = null;
for (InputMethodInfo info : infos) {
if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
id = info.getId();
}
}
if (id == null) {
throw new RuntimeException(String.format(
"Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
}
im.setInputMethod(null, id);
}
private void restoreActiveIme() throws RemoteException {
// TODO: figure out a way to restore active IME
// Currently retrieving active IME requires querying secure settings provider, which is hard
// to do without a Context; so the caveat here is that to make the post test device usable,
// the active IME needs to be manually switched.
}
}

View File

@ -0,0 +1,42 @@
/*
* 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 com.android.uiautomator.testrunner;
import com.android.uiautomator.testrunner.TestCaseCollector.TestCaseFilter;
import java.lang.reflect.Method;
/**
* A {@link TestCaseFilter} that accepts testFoo methods and {@link UiAutomatorTestCase} classes
*
* @hide
*/
public class UiAutomatorTestCaseFilter implements TestCaseFilter {
@Override
public boolean accept(Method method) {
return ((method.getParameterTypes().length == 0) &&
(method.getName().startsWith("test")) &&
(method.getReturnType().getSimpleName().equals("void")));
}
@Override
public boolean accept(Class<?> clazz) {
return UiAutomatorTestCase.class.isAssignableFrom(clazz);
}
}

View File

@ -0,0 +1,431 @@
/*
* 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 com.android.uiautomator.testrunner;
import android.app.Activity;
import android.app.IInstrumentationWatcher;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Debug;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.test.RepetitiveTest;
import android.util.Log;
import com.android.uiautomator.core.ShellUiAutomatorBridge;
import com.android.uiautomator.core.Tracer;
import com.android.uiautomator.core.UiAutomationShellWrapper;
import com.android.uiautomator.core.UiDevice;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.runner.BaseTestRunner;
import junit.textui.ResultPrinter;
/**
* @hide
*/
public class UiAutomatorTestRunner {
private static final String LOGTAG = UiAutomatorTestRunner.class.getSimpleName();
private static final int EXIT_OK = 0;
private static final int EXIT_EXCEPTION = -1;
private static final String HANDLER_THREAD_NAME = "UiAutomatorHandlerThread";
private boolean mDebug;
private boolean mMonkey;
private Bundle mParams = null;
private UiDevice mUiDevice;
private List<String> mTestClasses = null;
private final FakeInstrumentationWatcher mWatcher = new FakeInstrumentationWatcher();
private final IAutomationSupport mAutomationSupport = new IAutomationSupport() {
@Override
public void sendStatus(int resultCode, Bundle status) {
mWatcher.instrumentationStatus(null, resultCode, status);
}
};
private final List<TestListener> mTestListeners = new ArrayList<TestListener>();
private HandlerThread mHandlerThread;
public void run(List<String> testClasses, Bundle params, boolean debug, boolean monkey) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(LOGTAG, "uncaught exception", ex);
Bundle results = new Bundle();
results.putString("shortMsg", ex.getClass().getName());
results.putString("longMsg", ex.getMessage());
mWatcher.instrumentationFinished(null, 0, results);
// bailing on uncaught exception
System.exit(EXIT_EXCEPTION);
}
});
mTestClasses = testClasses;
mParams = params;
mDebug = debug;
mMonkey = monkey;
start();
System.exit(EXIT_OK);
}
/**
* Called after all test classes are in place, ready to test
*/
protected void start() {
TestCaseCollector collector = getTestCaseCollector(this.getClass().getClassLoader());
try {
collector.addTestClasses(mTestClasses);
} catch (ClassNotFoundException e) {
// will be caught by uncaught handler
throw new RuntimeException(e.getMessage(), e);
}
if (mDebug) {
Debug.waitForDebugger();
}
mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
mHandlerThread.setDaemon(true);
mHandlerThread.start();
UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
automationWrapper.connect();
long startTime = SystemClock.uptimeMillis();
TestResult testRunResult = new TestResult();
ResultReporter resultPrinter;
String outputFormat = mParams.getString("outputFormat");
List<TestCase> testCases = collector.getTestCases();
Bundle testRunOutput = new Bundle();
if ("simple".equals(outputFormat)) {
resultPrinter = new SimpleResultPrinter(System.out, true);
} else {
resultPrinter = new WatcherResultPrinter(testCases.size());
}
try {
automationWrapper.setRunAsMonkey(mMonkey);
mUiDevice = UiDevice.getInstance();
mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
String traceType = mParams.getString("traceOutputMode");
if(traceType != null) {
Tracer.Mode mode = Tracer.Mode.valueOf(Tracer.Mode.class, traceType);
if (mode == Tracer.Mode.FILE || mode == Tracer.Mode.ALL) {
String filename = mParams.getString("traceLogFilename");
if (filename == null) {
throw new RuntimeException("Name of log file not specified. " +
"Please specify it using traceLogFilename parameter");
}
Tracer.getInstance().setOutputFilename(filename);
}
Tracer.getInstance().setOutputMode(mode);
}
// add test listeners
testRunResult.addListener(resultPrinter);
// add all custom listeners
for (TestListener listener : mTestListeners) {
testRunResult.addListener(listener);
}
// run tests for realz!
for (TestCase testCase : testCases) {
prepareTestCase(testCase);
testCase.run(testRunResult);
}
} catch (Throwable t) {
// catch all exceptions so a more verbose error message can be outputted
resultPrinter.printUnexpectedError(t);
testRunOutput.putString("shortMsg", t.getMessage());
} finally {
long runTime = SystemClock.uptimeMillis() - startTime;
resultPrinter.print(testRunResult, runTime, testRunOutput);
automationWrapper.disconnect();
automationWrapper.setRunAsMonkey(false);
mHandlerThread.quit();
}
}
// copy & pasted from com.android.commands.am.Am
private class FakeInstrumentationWatcher implements IInstrumentationWatcher {
private final boolean mRawMode = true;
@Override
public IBinder asBinder() {
throw new UnsupportedOperationException("I'm just a fake!");
}
@Override
public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.print(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println("INSTRUMENTATION_STATUS: " + key + "="
+ results.get(key));
}
}
System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
}
notifyAll();
}
}
@Override
public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.println(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println("INSTRUMENTATION_RESULT: " + key + "="
+ results.get(key));
}
}
System.out.println("INSTRUMENTATION_CODE: " + resultCode);
}
notifyAll();
}
}
}
private interface ResultReporter extends TestListener {
public void print(TestResult result, long runTime, Bundle testOutput);
public void printUnexpectedError(Throwable t);
}
// Copy & pasted from InstrumentationTestRunner.WatcherResultPrinter
private class WatcherResultPrinter implements ResultReporter {
private static final String REPORT_KEY_NUM_TOTAL = "numtests";
private static final String REPORT_KEY_NAME_CLASS = "class";
private static final String REPORT_KEY_NUM_CURRENT = "current";
private static final String REPORT_KEY_NAME_TEST = "test";
private static final String REPORT_KEY_NUM_ITERATIONS = "numiterations";
private static final String REPORT_VALUE_ID = "UiAutomatorTestRunner";
private static final String REPORT_KEY_STACK = "stack";
private static final int REPORT_VALUE_RESULT_START = 1;
private static final int REPORT_VALUE_RESULT_ERROR = -1;
private static final int REPORT_VALUE_RESULT_FAILURE = -2;
private final Bundle mResultTemplate;
Bundle mTestResult;
int mTestNum = 0;
int mTestResultCode = 0;
String mTestClass = null;
private final SimpleResultPrinter mPrinter;
private final ByteArrayOutputStream mStream;
private final PrintStream mWriter;
public WatcherResultPrinter(int numTests) {
mResultTemplate = new Bundle();
mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
mStream = new ByteArrayOutputStream();
mWriter = new PrintStream(mStream);
mPrinter = new SimpleResultPrinter(mWriter, false);
}
/**
* send a status for the start of a each test, so long tests can be seen
* as "running"
*/
@Override
public void startTest(Test test) {
String testClass = test.getClass().getName();
String testName = ((TestCase) test).getName();
mTestResult = new Bundle(mResultTemplate);
mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
// pretty printing
if (testClass != null && !testClass.equals(mTestClass)) {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\n%s:", testClass));
mTestClass = testClass;
} else {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
}
Method testMethod = null;
try {
testMethod = test.getClass().getMethod(testName);
// Report total number of iterations, if test is repetitive
if (testMethod.isAnnotationPresent(RepetitiveTest.class)) {
int numIterations = testMethod.getAnnotation(RepetitiveTest.class)
.numIterations();
mTestResult.putInt(REPORT_KEY_NUM_ITERATIONS, numIterations);
}
} catch (NoSuchMethodException e) {
// ignore- the test with given name does not exist. Will be
// handled during test
// execution
}
mAutomationSupport.sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
mTestResultCode = 0;
mPrinter.startTest(test);
}
@Override
public void addError(Test test, Throwable t) {
mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
mTestResultCode = REPORT_VALUE_RESULT_ERROR;
// pretty printing
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nError in %s:\n%s",
((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
mPrinter.addError(test, t);
}
@Override
public void addFailure(Test test, AssertionFailedError t) {
mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
// pretty printing
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nFailure in %s:\n%s",
((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
mPrinter.addFailure(test, t);
}
@Override
public void endTest(Test test) {
if (mTestResultCode == 0) {
mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
}
mAutomationSupport.sendStatus(mTestResultCode, mTestResult);
mPrinter.endTest(test);
}
@Override
public void print(TestResult result, long runTime, Bundle testOutput) {
mPrinter.print(result, runTime, testOutput);
testOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format("\nTest results for %s=%s",
getClass().getSimpleName(),
mStream.toString()));
mWriter.close();
mAutomationSupport.sendStatus(Activity.RESULT_OK, testOutput);
}
@Override
public void printUnexpectedError(Throwable t) {
mWriter.println(String.format("Test run aborted due to unexpected exception: %s",
t.getMessage()));
t.printStackTrace(mWriter);
}
}
/**
* Class that produces the same output as JUnit when running from command line. Can be
* used when default UiAutomator output is too verbose.
*/
private class SimpleResultPrinter extends ResultPrinter implements ResultReporter {
private final boolean mFullOutput;
public SimpleResultPrinter(PrintStream writer, boolean fullOutput) {
super(writer);
mFullOutput = fullOutput;
}
@Override
public void print(TestResult result, long runTime, Bundle testOutput) {
printHeader(runTime);
if (mFullOutput) {
printErrors(result);
printFailures(result);
}
printFooter(result);
}
@Override
public void printUnexpectedError(Throwable t) {
if (mFullOutput) {
getWriter().printf("Test run aborted due to unexpected exeption: %s",
t.getMessage());
t.printStackTrace(getWriter());
}
}
}
protected TestCaseCollector getTestCaseCollector(ClassLoader classLoader) {
return new TestCaseCollector(classLoader, getTestCaseFilter());
}
/**
* Returns an object which determines if the class and its methods should be
* accepted into the test suite.
* @return
*/
public UiAutomatorTestCaseFilter getTestCaseFilter() {
return new UiAutomatorTestCaseFilter();
}
protected void addTestListener(TestListener listener) {
if (!mTestListeners.contains(listener)) {
mTestListeners.add(listener);
}
}
protected void removeTestListener(TestListener listener) {
mTestListeners.remove(listener);
}
/**
* subclass may override this method to perform further preparation
*
* @param testCase
*/
protected void prepareTestCase(TestCase testCase) {
((UiAutomatorTestCase)testCase).setAutomationSupport(mAutomationSupport);
((UiAutomatorTestCase)testCase).setUiDevice(mUiDevice);
((UiAutomatorTestCase)testCase).setParams(mParams);
}
}

17
tests/utils/Android.mk Normal file
View File

@ -0,0 +1,17 @@
#
# 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.
#
include $(call all-subdir-makefiles)

View File

@ -0,0 +1,26 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := DummyIME
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,35 @@
<!--
/*
* Copyright 2006, 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.
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.testing.dummyime">
<application android:label="Dummy IME">
<service android:name="DummyIme"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
<activity android:name=".ImePreferences" android:label="Dummy IME Settings">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* 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.
*/
-->
<!-- The attributes in this XML file provide configuration information -->
<!-- for the Search Manager. -->
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.android.testing.dummyime.ImePreferences">
<subtype
android:label="Generic"
android:imeSubtypeLocale="en_US"
android:imeSubtypeMode="keyboard" />
</input-method>

View File

@ -0,0 +1,35 @@
/*
* 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 com.android.testing.dummyime;
import android.inputmethodservice.InputMethodService;
/**
* Dummy IME implementation that basically does nothing
*/
public class DummyIme extends InputMethodService {
@Override
public boolean onEvaluateFullscreenMode() {
return false;
}
@Override
public boolean onEvaluateInputViewShown() {
return false;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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 com.android.testing.dummyime;
import android.preference.PreferenceActivity;
/**
* Dummy IME preference activity
*/
public class ImePreferences extends PreferenceActivity {
}

View File

@ -0,0 +1,26 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += \
src/com/android/testing/alarmservice/Alarm.aidl
LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
LOCAL_SDK_VERSION := 7
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.testing.alarmservice" >
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:label="Sleep Utils Alarm Service">
<service android:name=".AlarmService"
android:label="Sleep Utils Alarm Service"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.android.testing.ALARM_SERVICE" />
</intent-filter>
</service>
<receiver android:name=".WakeUpCall">
<intent-filter>
<action android:name="com.android.testing.alarmservice.WAKEUP" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,23 @@
/*
* 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 com.android.testing.alarmservice;
interface Alarm {
int prepare();
int setAlarmAndWait(long timeoutMills);
int done();
}

View File

@ -0,0 +1,77 @@
/*
* 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 com.android.testing.alarmservice;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import com.android.testing.alarmservice.Alarm.Stub;
public class AlarmImpl extends Stub {
private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
private Context mContext;
public AlarmImpl(Context context) {
super();
mContext = context;
}
@Override
public int prepare() throws RemoteException {
WakeUpController.getController().getWakeLock().acquire();
Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
return 0;
}
@Override
public int setAlarmAndWait(long timeoutMills) throws RemoteException {
// calculate when device should be waken up
long atTime = SystemClock.elapsedRealtime() + timeoutMills;
AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
// set alarm, which will be delivered in form of the wakeupIntent
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
Object lock = WakeUpController.getController().getWakeSync();
// release wakelock and wait for the lock to be poked from the broadcast receiver
WakeUpController.getController().getWakeLock().release();
// does not really matter if device enters suspend before we start waiting on lock
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
}
}
Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
return 0;
}
@Override
public int done() throws RemoteException {
WakeUpController.getController().getWakeLock().release();
return 0;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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 com.android.testing.alarmservice;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
public class AlarmService extends Service {
private AlarmImpl mAlarmImpl = null;
static Context sContext;
@Override
public void onCreate() {
super.onCreate();
sContext = this;
}
@Override
public IBinder onBind(Intent intent) {
return getAlarmImpl();
}
private AlarmImpl getAlarmImpl() {
if (mAlarmImpl == null) {
mAlarmImpl = new AlarmImpl(this);
}
return mAlarmImpl;
}
@Override
public void onDestroy() {
sContext = null;
super.onDestroy();
}
}

View File

@ -0,0 +1,42 @@
/*
* 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 com.android.testing.alarmservice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* The receiver for the alarm we set
*
*/
public class WakeUpCall extends BroadcastReceiver {
public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
@Override
public void onReceive(Context context, Intent intent) {
// we acquire wakelock without release because user is supposed to manually release it
WakeUpController.getController().getWakeLock().acquire();
Object lock = WakeUpController.getController().getWakeSync();
synchronized (lock) {
// poke the lock so the service side can be woken from waiting on the lock
lock.notifyAll();
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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 com.android.testing.alarmservice;
import android.content.Context;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
/**
* A singleton used for controlling and sharing of states/wakelocks
*
*/
public class WakeUpController {
private static final String LOG_TAG = WakeUpController.class.getName();
private static WakeUpController mController = null;
private WakeLock mWakeLock = null;
private Object mWakeSync = new Object();
private WakeUpController() {
Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
}
public static synchronized WakeUpController getController() {
if (mController == null) {
mController = new WakeUpController();
}
return mController;
}
public WakeLock getWakeLock() {
if (mWakeLock == null) {
PowerManager pm =
(PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
}
return mWakeLock;
}
public Object getWakeSync() {
return mWakeSync;
}
}

View File

@ -0,0 +1,2 @@
LOCAL_PATH:= $(call my-dir)
include $(call all-makefiles-under, $(LOCAL_PATH))

View File

@ -0,0 +1,23 @@
This folder contains utils to properly perform timed suspend and wakeup.
AlarmService - a service that client can bind to and perform:
1) holding wakelock (singleton to this service)
2) setting alarm for a specified period and releasing the wakelock; service
call will block until alarm has been triggered and the wakelock is held
3) releasing the wakelock
SleepHelper - a self instrumentation meant as a convenient way to trigger
the service functions from command line. Corresponding to service function
above, supported operations are:
1) holding wakelock
am instrument -w -e command prepare \
com.android.testing.sleephelper/.SetAlarm
2) setting alarm and wait til triggered
am instrument -w -e command set_wait \
-e param <time in ms> com.android.testing.sleephelper/.SetAlarm
Note: for the function to work properly, "-w" parameter is required
3) releasing wakelock
am instrument -w -e command done \
com.android.testing.sleephelper/.SetAlarm

View File

@ -0,0 +1,29 @@
#
# 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += \
../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
LOCAL_SDK_VERSION := 7
LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.testing.sleephelper">
<uses-sdk android:minSdkVersion="7" />
<instrumentation android:label="Sleep Helper"
android:name="com.android.testing.sleephelper.SetAlarm"
android:targetPackage="com.android.testing.sleephelper" />
<application android:label="Sleep Utils Sleep Helper">
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@ -0,0 +1,152 @@
/*
* 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 com.android.testing.sleephelper;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.testing.alarmservice.Alarm;
public class SetAlarm extends Instrumentation {
private static final String COMMAND = "command";
private static final String PARAM = "param";
private static final String CMD_PREPARE = "prepare";
private static final String CMD_SET = "set_wait";
private static final String CMD_DONE = "done";
private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
private static final String SERVICE_PKG = "com.android.testing.alarmservice";
private static final String LOG_TAG = SetAlarm.class.getSimpleName();
private Alarm mAlarmService = null;
private Bundle mArgs = null;
private String mCommand = null;
private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(LOG_TAG, "Service disconnected.");
mAlarmService = null;
errorFinish("service disconnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(LOG_TAG, "Service connected.");
mAlarmService = Alarm.Stub.asInterface(service);
handleCommands();
}
};
private void handleCommands() {
if (CMD_PREPARE.equals(mCommand)) {
callPrepare();
} else if (CMD_SET.equals(mCommand)) {
String paramString = mArgs.getString(PARAM);
if (paramString == null) {
errorFinish("argument expected for alarm time");
}
long timeout = -1;
try {
timeout = Long.parseLong(paramString);
} catch (NumberFormatException nfe) {
errorFinish("a number argument is expected");
}
callSetAndWait(timeout);
} else if (CMD_DONE.equals(mCommand)) {
callDone();
} else {
errorFinish("Unrecognized command: " + mCommand);
}
finish(Activity.RESULT_OK, new Bundle());
}
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
mCommand = arguments.getString(COMMAND);
if ("true".equals(arguments.getString("debug"))) {
Debug.waitForDebugger();
}
if (mCommand == null) {
errorFinish("No command specified");
}
mArgs = arguments;
connectToAlarmService();
}
private void errorFinish(String msg) {
Bundle ret = new Bundle();
ret.putString("error", msg);
finish(Activity.RESULT_CANCELED, ret);
}
private void connectToAlarmService() {
// start the service with an intent, this ensures the service keeps running after unbind
ComponentName cn = getContext().startService(mServceIntent);
if (cn == null) {
errorFinish("failed to start service");
}
if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
errorFinish("failed to bind service");
}
}
private void callPrepare() {
try {
mAlarmService.prepare();
} catch (RemoteException e) {
errorFinish("RemoteExeption in prepare()");
} finally {
getContext().unbindService(mConn);
}
}
private void callDone() {
try {
mAlarmService.done();
} catch (RemoteException e) {
errorFinish("RemoteExeption in prepare()");
} finally {
getContext().unbindService(mConn);
}
// explicitly stop the service (started in prepare()) so that the service is now free
// to be reclaimed
getContext().stopService(mServceIntent);
}
private void callSetAndWait(long timeoutMills) {
try {
mAlarmService.setAlarmAndWait(timeoutMills);
} catch (RemoteException e) {
errorFinish("RemoteExeption in setAlarmAndWait()");
} finally {
getContext().unbindService(mConn);
}
}
}

View File

@ -0,0 +1,24 @@
#
# Copyright (C) 2014 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := WakeupLoopService
LOCAL_SDK_VERSION := 7
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.test.wakeuploop" >
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:label="Auto Wakeup Loop">
<service android:name=".WakeLoopService"
android:label="Wakup Loop Service"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
</intent-filter>
</service>
<receiver android:name=".WakeUpCall">
<intent-filter>
<action android:name="android.test.wakeuploop.WAKEUP" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2014 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.test.wakeuploop;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileUtil {
private static FileUtil sInst = null;
private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
private FileUtil() {};
public static FileUtil get() {
if (sInst == null) {
sInst = new FileUtil();
}
return sInst;
}
public void writeDateToFile(File file) {
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(sDateFormat.format(new Date()).getBytes());
fos.write('\n');
fos.flush();
fos.close();
} catch (IOException ioe) {
Log.e("FileUtil", "exception writing date to file", ioe);
}
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2014 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.test.wakeuploop;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.SystemClock;
import android.util.Log;
import java.io.File;
public class WakeLoopService extends Service {
private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
static final String MAX_LOOP = "MAX_LOOP";
static final String STOP_CALLBACK = "STOP_CALLBACK";
static final String THIS_LOOP = "THIS_LOOP";
static final int MSG_STOP_SERVICE = 0xd1ed1e;
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == MSG_STOP_SERVICE) {
stopSelf();
} else {
super.handleMessage(msg);
}
};
};
@Override
public IBinder onBind(Intent intent) {
// no binding, just start via intent
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// get wakeup interval from intent
long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
if (wakeupInterval == 0) {
// stop and error
Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
stopSelf();
return START_NOT_STICKY;
}
FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
"wakeup-loop-start.txt"));
Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
wakeupInterval, maxLoop));
// calculate when device should be waken up
long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
.putExtra(WAKEUP_INTERNAL, wakeupInterval)
.putExtra(MAX_LOOP, maxLoop)
.putExtra(THIS_LOOP, 0L)
.putExtra(STOP_CALLBACK, new Messenger(mHandler));
PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// set alarm, which will be delivered in form of the wakeupIntent
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
Log.d(LOG_TAG, "WakeLoop: STOPPED");
// cancel alarms first
Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
.putExtra(WakeUpCall.CANCEL, "true");
sendBroadcast(intent);
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2014 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.test.wakeuploop;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import java.io.File;
/**
* The receiver for the alarm we set
*
*/
public class WakeUpCall extends BroadcastReceiver {
private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
static final String CANCEL = "CANCEL";
@Override
public void onReceive(Context context, Intent intent) {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
boolean cancel = intent.hasExtra(CANCEL);
if (!cancel) {
long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
wakeupInterval, maxLoop, thisLoop));
if (thisLoop == -1) {
Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
stopService(intent);
return;
}
if (wakeupInterval == 0) {
Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
stopService(intent);
return;
}
thisLoop++;
Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
if (thisLoop == maxLoop) {
// when maxLoop is 0, we loop forever, so not checking that case
// here
Log.d(LOG_TAG, "reached max loop count, stopping service");
stopService(intent);
return;
}
screenOn(context);
FileUtil.get().writeDateToFile(
new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
// calculate when device should be waken up
long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// set alarm, which will be delivered in form of the wakeupIntent
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
} else {
// cancel alarms
Log.d(LOG_TAG, "cancelling future alarms on request");
am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
}
}
private void stopService(Intent i) {
Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
if (msgr == null) {
Log.e(LOG_TAG, "no stop service callback found, cannot stop");
} else {
Message msg = new Message();
msg.what = WakeLoopService.MSG_STOP_SERVICE;
try {
msgr.send(msg);
} catch (RemoteException e) {
Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
}
}
}
private void screenOn(Context context) {
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@SuppressWarnings("deprecation")
WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
wl.acquire(500);
}
}