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:
25
cmds/uiautomator/Android.mk
Normal file
25
cmds/uiautomator/Android.mk
Normal 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
|
0
cmds/uiautomator/MODULE_LICENSE_APACHE2
Normal file
0
cmds/uiautomator/MODULE_LICENSE_APACHE2
Normal file
174
cmds/uiautomator/api/16.txt
Normal file
174
cmds/uiautomator/api/16.txt
Normal 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
192
cmds/uiautomator/api/17.txt
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
222
cmds/uiautomator/api/current.txt
Normal file
222
cmds/uiautomator/api/current.txt
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
0
cmds/uiautomator/api/removed.txt
Normal file
0
cmds/uiautomator/api/removed.txt
Normal file
17
cmds/uiautomator/cmds/Android.mk
Normal file
17
cmds/uiautomator/cmds/Android.mk
Normal 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)
|
33
cmds/uiautomator/cmds/uiautomator/Android.mk
Normal file
33
cmds/uiautomator/cmds/uiautomator/Android.mk
Normal 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)
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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(),
|
||||||
|
};
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
cmds/uiautomator/cmds/uiautomator/uiautomator
Executable file
123
cmds/uiautomator/cmds/uiautomator/uiautomator
Executable 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}
|
29
cmds/uiautomator/instrumentation/Android.mk
Normal file
29
cmds/uiautomator/instrumentation/Android.mk
Normal 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)
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
132
cmds/uiautomator/library/Android.mk
Normal file
132
cmds/uiautomator/library/Android.mk
Normal 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 :=
|
17
cmds/uiautomator/library/apicheck_msg_current.txt
Normal file
17
cmds/uiautomator/library/apicheck_msg_current.txt
Normal 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.
|
||||||
|
******************************
|
||||||
|
|
||||||
|
|
||||||
|
|
7
cmds/uiautomator/library/apicheck_msg_last.txt
Normal file
7
cmds/uiautomator/library/apicheck_msg_last.txt
Normal 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.
|
||||||
|
******************************
|
||||||
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
@ -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();
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
17
tests/utils/Android.mk
Normal 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)
|
26
tests/utils/DummyIME/Android.mk
Normal file
26
tests/utils/DummyIME/Android.mk
Normal 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)
|
35
tests/utils/DummyIME/AndroidManifest.xml
Normal file
35
tests/utils/DummyIME/AndroidManifest.xml
Normal 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>
|
29
tests/utils/DummyIME/res/xml/method.xml
Normal file
29
tests/utils/DummyIME/res/xml/method.xml
Normal 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>
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
26
tests/utils/SleepUtils/AlarmService/Android.mk
Normal file
26
tests/utils/SleepUtils/AlarmService/Android.mk
Normal 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)
|
31
tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
Normal file
31
tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
Normal 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>
|
@ -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();
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
2
tests/utils/SleepUtils/Android.mk
Normal file
2
tests/utils/SleepUtils/Android.mk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(call all-makefiles-under, $(LOCAL_PATH))
|
23
tests/utils/SleepUtils/README
Normal file
23
tests/utils/SleepUtils/README
Normal 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
|
29
tests/utils/SleepUtils/SleepHelper/Android.mk
Normal file
29
tests/utils/SleepUtils/SleepHelper/Android.mk
Normal 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)
|
21
tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
Normal file
21
tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
Normal 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>
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
tests/utils/SleepUtils/WakeLoopService/Android.mk
Normal file
24
tests/utils/SleepUtils/WakeLoopService/Android.mk
Normal 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)
|
32
tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
Normal file
32
tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
Normal 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>
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user