Merge uiautomator/ from platform/frameworks/testing to /
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..5391305
--- /dev/null
+++ b/Android.mk
@@ -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
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/api/16.txt b/api/16.txt
new file mode 100644
index 0000000..f3b0eb7
--- /dev/null
+++ b/api/16.txt
@@ -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);
+  }
+
+}
+
diff --git a/api/17.txt b/api/17.txt
new file mode 100644
index 0000000..a1d80c4
--- /dev/null
+++ b/api/17.txt
@@ -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);
+  }
+
+}
+
diff --git a/api/current.txt b/api/current.txt
new file mode 100644
index 0000000..7eeecf5
--- /dev/null
+++ b/api/current.txt
@@ -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);
+  }
+
+}
+
diff --git a/cmds/Android.mk b/cmds/Android.mk
new file mode 100644
index 0000000..c141484
--- /dev/null
+++ b/cmds/Android.mk
@@ -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)
diff --git a/cmds/uiautomator/Android.mk b/cmds/uiautomator/Android.mk
new file mode 100644
index 0000000..5c91b52
--- /dev/null
+++ b/cmds/uiautomator/Android.mk
@@ -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)
diff --git a/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
new file mode 100644
index 0000000..c35f7fc
--- /dev/null
+++ b/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -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()));
+    }
+}
diff --git a/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java b/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
new file mode 100644
index 0000000..ce55f18
--- /dev/null
+++ b/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
@@ -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();
+    }
+}
diff --git a/cmds/uiautomator/src/com/android/commands/uiautomator/Launcher.java b/cmds/uiautomator/src/com/android/commands/uiautomator/Launcher.java
new file mode 100644
index 0000000..bc1d948
--- /dev/null
+++ b/cmds/uiautomator/src/com/android/commands/uiautomator/Launcher.java
@@ -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(),
+    };
+}
\ No newline at end of file
diff --git a/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java b/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
new file mode 100644
index 0000000..65611ab
--- /dev/null
+++ b/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
@@ -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";
+    }
+
+}
diff --git a/cmds/uiautomator/uiautomator b/cmds/uiautomator/uiautomator
new file mode 100755
index 0000000..9aec2c4
--- /dev/null
+++ b/cmds/uiautomator/uiautomator
@@ -0,0 +1,120 @@
+#
+# 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}" ]; then
+      echo "Error: more parameters expected for runtest; please see usage for details"
+      cmd="help"
+      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}
diff --git a/instrumentation/Android.mk b/instrumentation/Android.mk
new file mode 100644
index 0000000..0c93b4c
--- /dev/null
+++ b/instrumentation/Android.mk
@@ -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)
diff --git a/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java b/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java
new file mode 100644
index 0000000..1d4fb93
--- /dev/null
+++ b/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java
@@ -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();
+    }
+}
diff --git a/instrumentation/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
new file mode 100644
index 0000000..f0c60d2
--- /dev/null
+++ b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
@@ -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);
+
+}
diff --git a/instrumentation/testrunner-src/com/android/uiautomator/testrunner/InstrumentationAutomationSupport.java b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/InstrumentationAutomationSupport.java
new file mode 100644
index 0000000..a70586e
--- /dev/null
+++ b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/InstrumentationAutomationSupport.java
@@ -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);
+    }
+}
diff --git a/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java
new file mode 100644
index 0000000..ae763f2
--- /dev/null
+++ b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java
@@ -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;
+    }
+}
diff --git a/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
new file mode 100644
index 0000000..b5f21c9
--- /dev/null
+++ b/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -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);
+    }
+}
diff --git a/library/Android.mk b/library/Android.mk
new file mode 100644
index 0000000..470e8ab
--- /dev/null
+++ b/library/Android.mk
@@ -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.
+#
+
+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
+
+###############################################
+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)
+
+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/testing/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), \
+    $(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), \
+    $(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: $(uiautomator_internal_api_file) | $(ACP)
+	@echo Copying uiautomator current.txt
+	$(hide) $(ACP) $< $(PRIVATE_API_DIR)/current.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 :=
diff --git a/library/apicheck_msg_current.txt b/library/apicheck_msg_current.txt
new file mode 100644
index 0000000..989248d
--- /dev/null
+++ b/library/apicheck_msg_current.txt
@@ -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.
+******************************
+
+
+
diff --git a/library/apicheck_msg_last.txt b/library/apicheck_msg_last.txt
new file mode 100644
index 0000000..2993157
--- /dev/null
+++ b/library/apicheck_msg_last.txt
@@ -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.
+******************************
+
+
diff --git a/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
new file mode 100644
index 0000000..63c51e8
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -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();
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java b/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
new file mode 100644
index 0000000..54835e3
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
@@ -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;
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/Configurator.java b/library/core-src/com/android/uiautomator/core/Configurator.java
new file mode 100644
index 0000000..249f404
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/Configurator.java
@@ -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;
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/InteractionController.java b/library/core-src/com/android/uiautomator/core/InteractionController.java
new file mode 100644
index 0000000..73e46f1
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -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);
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/QueryController.java b/library/core-src/com/android/uiautomator/core/QueryController.java
new file mode 100644
index 0000000..6931528
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/QueryController.java
@@ -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();
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/Tracer.java b/library/core-src/com/android/uiautomator/core/Tracer.java
new file mode 100644
index 0000000..d574fc0
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/Tracer.java
@@ -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());
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java b/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
new file mode 100644
index 0000000..bc5bc8e
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
@@ -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();
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiCollection.java b/library/core-src/com/android/uiautomator/core/UiCollection.java
new file mode 100644
index 0000000..e15beb2
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiCollection.java
@@ -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);
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiDevice.java b/library/core-src/com/android/uiautomator/core/UiDevice.java
new file mode 100644
index 0000000..a930eb4
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -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);
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiObject.java b/library/core-src/com/android/uiautomator/core/UiObject.java
new file mode 100644
index 0000000..4bb99cd
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiObject.java
@@ -0,0 +1,1083 @@
+/*
+ * 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.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * A UiObject is a representation of a view. It is not in any way directly bound to a
+ * view as an object reference. A UiObject contains information to help it
+ * locate a matching view at runtime based on the {@link UiSelector} properties specified in
+ * its constructor. Once you create an instance of a UiObject, it can
+ * be reused for different views that match the selector criteria.
+ * @since API Level 16
+ */
+public class UiObject {
+    private static final String LOG_TAG = UiObject.class.getSimpleName();
+    /**
+     * @since API Level 16
+     * @deprecated use {@link Configurator#setWaitForSelectorTimeout(long)}
+     **/
+    @Deprecated
+    protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000;
+    /**
+     * @since API Level 16
+     **/
+    protected static final long WAIT_FOR_SELECTOR_POLL = 1000;
+    // set a default timeout to 5.5s, since ANR threshold is 5s
+    /**
+     * @since API Level 16
+     **/
+    protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500;
+    /**
+     * @since API Level 16
+     **/
+    protected static final int SWIPE_MARGIN_LIMIT = 5;
+    /**
+     * @since API Level 17
+     * @deprecated use {@link Configurator#setScrollAcknowledgmentTimeout(long)}
+     **/
+    @Deprecated
+    protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000;
+    /**
+     * @since API Level 18
+     **/
+    protected static final int FINGER_TOUCH_HALF_WIDTH = 20;
+
+    private final UiSelector mSelector;
+
+    private final Configurator mConfig = Configurator.getInstance();
+
+    /**
+     * Constructs a UiObject to represent a view that matches the specified
+     * selector criteria.
+     * @param selector
+     * @since API Level 16
+     */
+    public UiObject(UiSelector selector) {
+        mSelector = selector;
+    }
+
+    /**
+     * Debugging helper. A test can dump the properties of a selector as a string
+     * to its logs if needed. <code>getSelector().toString();</code>
+     *
+     * @return {@link UiSelector}
+     * @since API Level 16
+     */
+    public final UiSelector getSelector() {
+        Tracer.trace();
+        return new UiSelector(mSelector);
+    }
+
+    /**
+     * Retrieves the {@link QueryController} to translate a {@link UiSelector} selector
+     * into an {@link AccessibilityNodeInfo}.
+     *
+     * @return {@link QueryController}
+     */
+    QueryController getQueryController() {
+        return UiDevice.getInstance().getAutomatorBridge().getQueryController();
+    }
+
+    /**
+     * Retrieves the {@link InteractionController} to perform finger actions such as tapping,
+     * swiping, or entering text.
+     *
+     * @return {@link InteractionController}
+     */
+    InteractionController getInteractionController() {
+        return UiDevice.getInstance().getAutomatorBridge().getInteractionController();
+    }
+
+    /**
+     * Creates a new UiObject for a child view that is under the present UiObject.
+     *
+     * @param selector for child view to match
+     * @return a new UiObject representing the child view
+     * @since API Level 16
+     */
+    public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException {
+        Tracer.trace(selector);
+        return new UiObject(getSelector().childSelector(selector));
+    }
+
+    /**
+     * Creates a new UiObject for a sibling view or a child of the sibling view, 
+     * relative to the present UiObject.
+     *
+     * @param selector for a sibling view or children of the sibling view
+     * @return a new UiObject representing the matched view
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException {
+        Tracer.trace(selector);
+        return new UiObject(getSelector().fromParent(selector));
+    }
+
+    /**
+     * Counts the child views immediately under the present UiObject.
+     *
+     * @return the count of child views.
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public int getChildCount() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.getChildCount();
+    }
+
+    /**
+     * Finds a matching UI element in the accessibility hierarchy, by
+     * using the selector for this UiObject.
+     *
+     * @param timeout in milliseconds
+     * @return AccessibilityNodeInfo if found else null
+     * @since API Level 16
+     */
+    protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) {
+        AccessibilityNodeInfo node = null;
+        long startMills = SystemClock.uptimeMillis();
+        long currentMills = 0;
+        while (currentMills <= timeout) {
+            node = getQueryController().findAccessibilityNodeInfo(getSelector());
+            if (node != null) {
+                break;
+            } else {
+                // does nothing if we're reentering another runWatchers()
+                UiDevice.getInstance().runWatchers();
+            }
+            currentMills = SystemClock.uptimeMillis() - startMills;
+            if(timeout > 0) {
+                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
+            }
+        }
+        return node;
+    }
+
+    /**
+     * Drags this object to a destination UiObject.
+     * The number of steps specified in your input parameter can influence the
+     * drag speed, and varying speeds may impact the results. Consider
+     * evaluating different speeds when using this method in your tests.
+     *
+     * @param destObj the destination UiObject.
+     * @param steps usually 40 steps. You can increase or decrease the steps to change the speed.
+     * @return true if successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean dragTo(UiObject destObj, int steps) throws UiObjectNotFoundException {
+        Rect srcRect = getVisibleBounds();
+        Rect dstRect = destObj.getVisibleBounds();
+        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(),
+                dstRect.centerX(), dstRect.centerY(), steps, true);
+    }
+
+    /**
+     * Drags this object to arbitrary coordinates.
+     * The number of steps specified in your input parameter can influence the
+     * drag speed, and varying speeds may impact the results. Consider
+     * evaluating different speeds when using this method in your tests.
+     *
+     * @param destX the X-axis coordinate.
+     * @param destY the Y-axis coordinate.
+     * @param steps usually 40 steps. You can increase or decrease the steps to change the speed.
+     * @return true if successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean dragTo(int destX, int destY, int steps) throws UiObjectNotFoundException {
+        Rect srcRect = getVisibleBounds();
+        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(), destX, destY,
+                steps, true);
+    }
+
+    /**
+     * Performs the swipe up action on the UiObject. 
+     * See also:
+     * <ul>
+     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
+     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
+     * <li>{@link UiScrollable#scrollBackward()}</li>
+     * <li>{@link UiScrollable#scrollForward()}</li>
+     * </ul>
+     *
+     * @param steps indicates the number of injected move steps into the system. Steps are
+     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
+     * @return true of successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean swipeUp(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
+        Rect rect = getVisibleBounds();
+        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
+            return false; // too small to swipe
+        return getInteractionController().swipe(rect.centerX(),
+                rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT,
+                steps);
+    }
+
+    /**
+     * Performs the swipe down action on the UiObject. 
+     * The swipe gesture can be performed over any surface. The targeted
+     * UI element does not need to be scrollable.
+     * See also:
+     * <ul>
+     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
+     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
+     * <li>{@link UiScrollable#scrollBackward()}</li>
+     * <li>{@link UiScrollable#scrollForward()}</li>
+     * </ul>
+     *
+     * @param steps indicates the number of injected move steps into the system. Steps are
+     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
+     * @return true if successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean swipeDown(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
+        Rect rect = getVisibleBounds();
+        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
+            return false; // too small to swipe
+        return getInteractionController().swipe(rect.centerX(),
+                rect.top + SWIPE_MARGIN_LIMIT, rect.centerX(),
+                rect.bottom - SWIPE_MARGIN_LIMIT, steps);
+    }
+
+    /**
+     * Performs the swipe left action on the UiObject. 
+     * The swipe gesture can be performed over any surface. The targeted
+     * UI element does not need to be scrollable.
+     * See also:
+     * <ul>
+     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
+     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
+     * <li>{@link UiScrollable#scrollBackward()}</li>
+     * <li>{@link UiScrollable#scrollForward()}</li>
+     * </ul>
+     *
+     * @param steps indicates the number of injected move steps into the system. Steps are
+     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
+     * @return true if successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
+        Rect rect = getVisibleBounds();
+        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
+            return false; // too small to swipe
+        return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
+                rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
+    }
+
+    /**
+     * Performs the swipe right action on the UiObject. 
+     * The swipe gesture can be performed over any surface. The targeted
+     * UI element does not need to be scrollable.
+     * See also:
+     * <ul>
+     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
+     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
+     * <li>{@link UiScrollable#scrollBackward()}</li>
+     * <li>{@link UiScrollable#scrollForward()}</li>
+     * </ul>
+     *
+     * @param steps indicates the number of injected move steps into the system. Steps are
+     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
+     * @return true if successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean swipeRight(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
+        Rect rect = getVisibleBounds();
+        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
+            return false; // too small to swipe
+        return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT,
+                rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
+    }
+
+    /**
+     * Finds the visible bounds of a partially visible UI element
+     *
+     * @param node
+     * @return null if node is null, else a Rect containing visible bounds
+     */
+    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
+        if (node == null) {
+            return null;
+        }
+
+        // targeted node's bounds
+        int w = UiDevice.getInstance().getDisplayWidth();
+        int h = UiDevice.getInstance().getDisplayHeight();
+        Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, w, h);
+
+        // is the targeted node within a scrollable container?
+        AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node);
+        if(scrollableParentNode == null) {
+            // nothing to adjust for so return the node's Rect as is
+            return nodeRect;
+        }
+
+        // Scrollable parent's visible bounds
+        Rect parentRect = AccessibilityNodeInfoHelper
+                .getVisibleBoundsInScreen(scrollableParentNode, w, h);
+        // adjust for partial clipping of targeted by parent node if required
+        nodeRect.intersect(parentRect);
+        return nodeRect;
+    }
+
+    /**
+     * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent
+     * indicates that this node might be in a container where it is partially
+     * visible due to scrolling. In this case, its clickable center might not be visible and
+     * the click coordinates should be adjusted.
+     *
+     * @param node
+     * @return The accessibility node info.
+     */
+    private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
+        AccessibilityNodeInfo parent = node;
+        while(parent != null) {
+            parent = parent.getParent();
+            if (parent != null && parent.isScrollable()) {
+                return parent;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Performs a click at the center of the visible bounds of the UI element represented
+     * by this UiObject.
+     *
+     * @return true id successful else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean click() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(),
+                mConfig.getActionAcknowledgmentTimeout());
+    }
+
+    /**
+     * Waits for window transitions that would typically take longer than the
+     * usual default timeouts.
+     * See {@link #clickAndWaitForNewWindow(long)}
+     *
+     * @return true if the event was triggered, else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
+        Tracer.trace();
+        return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
+    }
+
+    /**
+     * Performs a click at the center of the visible bounds of the UI element represented
+     * by this UiObject and waits for window transitions.
+     *
+     * This method differ from {@link UiObject#click()} only in that this method waits for a
+     * a new window transition as a result of the click. Some examples of a window transition:
+     * <li>launching a new activity</li>
+     * <li>bringing up a pop-up menu</li>
+     * <li>bringing up a dialog</li>
+     *
+     * @param timeout timeout before giving up on waiting for a new window
+     * @return true if the event was triggered, else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
+        Tracer.trace(timeout);
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY(),
+                mConfig.getActionAcknowledgmentTimeout());
+    }
+
+    /**
+     * Clicks the top and left corner of the UI element
+     *
+     * @return true on success
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean clickTopLeft() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5);
+    }
+
+    /**
+     * Long clicks bottom and right corner of the UI element
+     *
+     * @return true if operation was successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean longClickBottomRight() throws UiObjectNotFoundException  {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5);
+    }
+
+    /**
+     * Clicks the bottom and right corner of the UI element
+     *
+     * @return true on success
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean clickBottomRight() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5);
+    }
+
+    /**
+     * Long clicks the center of the visible bounds of the UI element
+     *
+     * @return true if operation was successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean longClick() throws UiObjectNotFoundException  {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY());
+    }
+
+    /**
+     * Long clicks on the top and left corner of the UI element
+     *
+     * @return true if operation was successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean longClickTopLeft() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5);
+    }
+
+    /**
+     * Reads the <code>text</code> property of the UI element
+     *
+     * @return text value of the current node represented by this UiObject
+     * @throws UiObjectNotFoundException if no match could be found
+     * @since API Level 16
+     */
+    public String getText() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        String retVal = safeStringReturn(node.getText());
+        Log.d(LOG_TAG, String.format("getText() = %s", retVal));
+        return retVal;
+    }
+
+    /**
+     * Retrieves the <code>className</code> property of the UI element.
+     *
+     * @return class name of the current node represented by this UiObject
+     * @throws UiObjectNotFoundException if no match was found
+     * @since API Level 18
+     */
+    public String getClassName() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        String retVal = safeStringReturn(node.getClassName());
+        Log.d(LOG_TAG, String.format("getClassName() = %s", retVal));
+        return retVal;
+    }
+
+    /**
+     * Reads the <code>content_desc</code> property of the UI element
+     *
+     * @return value of node attribute "content_desc"
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public String getContentDescription() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return safeStringReturn(node.getContentDescription());
+    }
+
+    /**
+     * Sets the text in an editable field, after clearing the field's content.
+     *
+     * The {@link UiSelector} selector of this object must reference a UI element that is editable.
+     *
+     * When you call this method, the method first simulates a {@link #click()} on
+     * editable field to set focus. The method then clears the field's contents
+     * and injects your specified text into the field.
+     *
+     * If you want to capture the original contents of the field, call {@link #getText()} first.
+     * You can then modify the text and use this method to update the field.
+     *
+     * @param text string to set
+     * @return true if operation is successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean setText(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
+        clearTextField();
+        return getInteractionController().sendText(text);
+    }
+
+    /**
+     * Clears the existing text contents in an editable field.
+     *
+     * The {@link UiSelector} of this object must reference a UI element that is editable.
+     *
+     * When you call this method, the method first sets focus at the start edge of the field.
+     * The method then simulates a long-press to select the existing text, and deletes the
+     * selected text.
+     *
+     * If a "Select-All" option is displayed, the method will automatically attempt to use it
+     * to ensure full text selection.
+     *
+     * Note that it is possible that not all the text in the field is selected; for example,
+     * if the text contains separators such as spaces, slashes, at symbol etc.
+     * Also, not all editable fields support the long-press functionality.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public void clearTextField() throws UiObjectNotFoundException {
+        Tracer.trace();
+        // long click left + center
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect rect = getVisibleBounds(node);
+        getInteractionController().longTapNoSync(rect.left + 20, rect.centerY());
+        // check if the edit menu is open
+        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
+        if(selectAll.waitForExists(50))
+            selectAll.click();
+        // wait for the selection
+        SystemClock.sleep(250);
+        // delete it
+        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
+    }
+
+    /**
+     * Check if the UI element's <code>checked</code> property is currently true
+     *
+     * @return true if it is else false
+     * @since API Level 16
+     */
+    public boolean isChecked() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isChecked();
+    }
+
+    /**
+     * Checks if the UI element's <code>selected</code> property is currently true.
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isSelected() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isSelected();
+    }
+
+    /**
+     * Checks if the UI element's <code>checkable</code> property is currently true.
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isCheckable() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isCheckable();
+    }
+
+    /**
+     * Checks if the UI element's <code>enabled</code> property is currently true.
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isEnabled() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isEnabled();
+    }
+
+    /**
+     * Checks if the UI element's <code>clickable</code> property is currently true.
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isClickable() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isClickable();
+    }
+
+    /**
+     * Check if the UI element's <code>focused</code> property is currently true
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isFocused() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isFocused();
+    }
+
+    /**
+     * Check if the UI element's <code>focusable</code> property is currently true.
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isFocusable() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isFocusable();
+    }
+
+    /**
+     * Check if the view's <code>scrollable</code> property is currently true
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isScrollable() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isScrollable();
+    }
+
+    /**
+     * Check if the view's <code>long-clickable</code> property is currently true
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public boolean isLongClickable() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return node.isLongClickable();
+    }
+
+    /**
+     * Reads the view's <code>package</code> property
+     *
+     * @return true if it is else false
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public String getPackageName() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return safeStringReturn(node.getPackageName());
+    }
+
+    /**
+     * Returns the visible bounds of the view.
+     *
+     * If a portion of the view is visible, only the bounds of the visible portion are
+     * reported.
+     *
+     * @return Rect
+     * @throws UiObjectNotFoundException
+     * @see {@link #getBounds()}
+     * @since API Level 17
+     */
+    public Rect getVisibleBounds() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        return getVisibleBounds(node);
+    }
+
+    /**
+     * Returns the view's <code>bounds</code> property. See {@link #getVisibleBounds()}
+     *
+     * @return Rect
+     * @throws UiObjectNotFoundException
+     * @since API Level 16
+     */
+    public Rect getBounds() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if(node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+        Rect nodeRect = new Rect();
+        node.getBoundsInScreen(nodeRect);
+
+        return nodeRect;
+    }
+
+    /**
+     * Waits a specified length of time for a view to become visible.
+     *
+     * This method waits until the view becomes visible on the display, or
+     * until the timeout has elapsed. You can use this method in situations where
+     * the content that you want to select is not immediately displayed.
+     *
+     * @param timeout the amount of time to wait (in milliseconds)
+     * @return true if the view is displayed, else false if timeout elapsed while waiting
+     * @since API Level 16
+     */
+    public boolean waitForExists(long timeout) {
+        Tracer.trace(timeout);
+        if(findAccessibilityNodeInfo(timeout) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Waits a specified length of time for a view to become undetectable.
+     *
+     * This method waits until a view is no longer matchable, or until the
+     * timeout has elapsed.
+     *
+     * A view becomes undetectable when the {@link UiSelector} of the object is
+     * unable to find a match because the element has either changed its state or is no
+     * longer displayed.
+     *
+     * You can use this method when attempting to wait for some long operation
+     * to compete, such as downloading a large file or connecting to a remote server.
+     *
+     * @param timeout time to wait (in milliseconds)
+     * @return true if the element is gone before timeout elapsed, else false if timeout elapsed
+     * but a matching element is still found.
+     * @since API Level 16
+     */
+    public boolean waitUntilGone(long timeout) {
+        Tracer.trace(timeout);
+        long startMills = SystemClock.uptimeMillis();
+        long currentMills = 0;
+        while (currentMills <= timeout) {
+            if(findAccessibilityNodeInfo(0) == null)
+                return true;
+            currentMills = SystemClock.uptimeMillis() - startMills;
+            if(timeout > 0)
+                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
+        }
+        return false;
+    }
+
+    /**
+     * Check if view exists.
+     *
+     * This methods performs a {@link #waitForExists(long)} with zero timeout. This
+     * basically returns immediately whether the view represented by this UiObject
+     * exists or not. If you need to wait longer for this view, then see
+     * {@link #waitForExists(long)}.
+     *
+     * @return true if the view represented by this UiObject does exist
+     * @since API Level 16
+     */
+    public boolean exists() {
+        Tracer.trace();
+        return waitForExists(0);
+    }
+
+    private String safeStringReturn(CharSequence cs) {
+        if(cs == null)
+            return "";
+        return cs.toString();
+    }
+
+    /**
+     * Performs a two-pointer gesture, where each pointer moves diagonally
+     * opposite across the other, from the center out towards the edges of the
+     * this UiObject.
+     * @param percent percentage of the object's diagonal length for the pinch gesture
+     * @param steps the number of steps for the gesture. Steps are injected 
+     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
+     * @return <code>true</code> if all touch events for this gesture are injected successfully,
+     *         <code>false</code> otherwise
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException {
+        // make value between 1 and 100
+        percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
+        float percentage = percent / 100f;
+
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if (node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+
+        Rect rect = getVisibleBounds(node);
+        if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
+            throw new IllegalStateException("Object width is too small for operation");
+
+        // start from the same point at the center of the control
+        Point startPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
+        Point startPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
+
+        // End at the top-left and bottom-right corners of the control
+        Point endPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
+                rect.centerY());
+        Point endPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
+                rect.centerY());
+
+        return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
+    }
+
+    /**
+     * Performs a two-pointer gesture, where each pointer moves diagonally
+     * toward the other, from the edges to the center of this UiObject .
+     * @param percent percentage of the object's diagonal length for the pinch gesture
+     * @param steps the number of steps for the gesture. Steps are injected 
+     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
+     * @return <code>true</code> if all touch events for this gesture are injected successfully,
+     *         <code>false</code> otherwise
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException {
+        // make value between 1 and 100
+        percent = (percent < 0) ? 0 : (percent > 100) ? 100 : percent;
+        float percentage = percent / 100f;
+
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
+        if (node == null) {
+            throw new UiObjectNotFoundException(getSelector().toString());
+        }
+
+        Rect rect = getVisibleBounds(node);
+        if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
+            throw new IllegalStateException("Object width is too small for operation");
+
+        Point startPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
+                rect.centerY());
+        Point startPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
+                rect.centerY());
+
+        Point endPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
+        Point endPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
+
+        return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
+    }
+
+    /**
+     * Generates a two-pointer gesture with arbitrary starting and ending points.
+     *
+     * @param startPoint1 start point of pointer 1
+     * @param startPoint2 start point of pointer 2
+     * @param endPoint1 end point of pointer 1
+     * @param endPoint2 end point of pointer 2
+     * @param steps the number of steps for the gesture. Steps are injected 
+     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
+     * @return <code>true</code> if all touch events for this gesture are injected successfully,
+     *         <code>false</code> otherwise
+     * @since API Level 18
+     */
+    public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1,
+            Point endPoint2, int steps) {
+
+        // avoid a divide by zero
+        if(steps == 0)
+            steps = 1;
+
+        final float stepX1 = (endPoint1.x - startPoint1.x) / steps;
+        final float stepY1 = (endPoint1.y - startPoint1.y) / steps;
+        final float stepX2 = (endPoint2.x - startPoint2.x) / steps;
+        final float stepY2 = (endPoint2.y - startPoint2.y) / steps;
+
+        int eventX1, eventY1, eventX2, eventY2;
+        eventX1 = startPoint1.x;
+        eventY1 = startPoint1.y;
+        eventX2 = startPoint2.x;
+        eventY2 = startPoint2.y;
+
+        // allocate for steps plus first down and last up
+        PointerCoords[] points1 = new PointerCoords[steps + 2];
+        PointerCoords[] points2 = new PointerCoords[steps + 2];
+
+        // Include the first and last touch downs in the arrays of steps
+        for (int i = 0; i < steps + 1; i++) {
+            PointerCoords p1 = new PointerCoords();
+            p1.x = eventX1;
+            p1.y = eventY1;
+            p1.pressure = 1;
+            p1.size = 1;
+            points1[i] = p1;
+
+            PointerCoords p2 = new PointerCoords();
+            p2.x = eventX2;
+            p2.y = eventY2;
+            p2.pressure = 1;
+            p2.size = 1;
+            points2[i] = p2;
+
+            eventX1 += stepX1;
+            eventY1 += stepY1;
+            eventX2 += stepX2;
+            eventY2 += stepY2;
+        }
+
+        // ending pointers coordinates
+        PointerCoords p1 = new PointerCoords();
+        p1.x = endPoint1.x;
+        p1.y = endPoint1.y;
+        p1.pressure = 1;
+        p1.size = 1;
+        points1[steps + 1] = p1;
+
+        PointerCoords p2 = new PointerCoords();
+        p2.x = endPoint2.x;
+        p2.y = endPoint2.y;
+        p2.pressure = 1;
+        p2.size = 1;
+        points2[steps + 1] = p2;
+
+        return performMultiPointerGesture(points1, points2);
+    }
+
+    /**
+     * Performs a multi-touch gesture. You must specify touch coordinates for
+     * at least 2 pointers. Each pointer must have all of its touch steps
+     * defined in an array of {@link PointerCoords}. You can use this method to
+     * specify complex gestures, like circles and irregular shapes, 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 represents the pointers' paths. Each {@link PointerCoords}
+     * array represents a different pointer. Each {@link PointerCoords} in an
+     * array element represents a touch point on a pointer's path.
+     * @return <code>true</code> if all touch events for this gesture are injected successfully,
+     *         <code>false</code> otherwise
+     * @since API Level 18
+     */
+    public boolean performMultiPointerGesture(PointerCoords[] ...touches) {
+        return getInteractionController().performMultiPointerGesture(touches);
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java b/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java
new file mode 100644
index 0000000..fc0891b
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java
@@ -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);
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiScrollable.java b/library/core-src/com/android/uiautomator/core/UiScrollable.java
new file mode 100644
index 0000000..a8d20c3
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiScrollable.java
@@ -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;
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiSelector.java b/library/core-src/com/android/uiautomator/core/UiSelector.java
new file mode 100644
index 0000000..bd61bfd
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiSelector.java
@@ -0,0 +1,1022 @@
+/*
+ * 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.SparseArray;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.regex.Pattern;
+
+/**
+ * Specifies the elements in the layout hierarchy for tests to target, filtered
+ * by properties such as text value, content-description, class name, and state
+ * information. You can also target an element by its location in a layout
+ * hierarchy.
+ * @since API Level 16
+ */
+public class UiSelector {
+    static final int SELECTOR_NIL = 0;
+    static final int SELECTOR_TEXT = 1;
+    static final int SELECTOR_START_TEXT = 2;
+    static final int SELECTOR_CONTAINS_TEXT = 3;
+    static final int SELECTOR_CLASS = 4;
+    static final int SELECTOR_DESCRIPTION = 5;
+    static final int SELECTOR_START_DESCRIPTION = 6;
+    static final int SELECTOR_CONTAINS_DESCRIPTION = 7;
+    static final int SELECTOR_INDEX = 8;
+    static final int SELECTOR_INSTANCE = 9;
+    static final int SELECTOR_ENABLED = 10;
+    static final int SELECTOR_FOCUSED = 11;
+    static final int SELECTOR_FOCUSABLE = 12;
+    static final int SELECTOR_SCROLLABLE = 13;
+    static final int SELECTOR_CLICKABLE = 14;
+    static final int SELECTOR_CHECKED = 15;
+    static final int SELECTOR_SELECTED = 16;
+    static final int SELECTOR_ID = 17;
+    static final int SELECTOR_PACKAGE_NAME = 18;
+    static final int SELECTOR_CHILD = 19;
+    static final int SELECTOR_CONTAINER = 20;
+    static final int SELECTOR_PATTERN = 21;
+    static final int SELECTOR_PARENT = 22;
+    static final int SELECTOR_COUNT = 23;
+    static final int SELECTOR_LONG_CLICKABLE = 24;
+    static final int SELECTOR_TEXT_REGEX = 25;
+    static final int SELECTOR_CLASS_REGEX = 26;
+    static final int SELECTOR_DESCRIPTION_REGEX = 27;
+    static final int SELECTOR_PACKAGE_NAME_REGEX = 28;
+    static final int SELECTOR_RESOURCE_ID = 29;
+    static final int SELECTOR_CHECKABLE = 30;
+    static final int SELECTOR_RESOURCE_ID_REGEX = 31;
+
+    private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>();
+
+    /**
+     * @since API Level 16
+     */
+    public UiSelector() {
+    }
+
+    UiSelector(UiSelector selector) {
+        mSelectorAttributes = selector.cloneSelector().mSelectorAttributes;
+    }
+
+    /**
+     * @since API Level 17
+     */
+    protected UiSelector cloneSelector() {
+        UiSelector ret = new UiSelector();
+        ret.mSelectorAttributes = mSelectorAttributes.clone();
+        if (hasChildSelector())
+            ret.mSelectorAttributes.put(SELECTOR_CHILD, new UiSelector(getChildSelector()));
+        if (hasParentSelector())
+            ret.mSelectorAttributes.put(SELECTOR_PARENT, new UiSelector(getParentSelector()));
+        if (hasPatternSelector())
+            ret.mSelectorAttributes.put(SELECTOR_PATTERN, new UiSelector(getPatternSelector()));
+        return ret;
+    }
+
+    static UiSelector patternBuilder(UiSelector selector) {
+        if (!selector.hasPatternSelector()) {
+            return new UiSelector().patternSelector(selector);
+        }
+        return selector;
+    }
+
+    static UiSelector patternBuilder(UiSelector container, UiSelector pattern) {
+        return new UiSelector(
+                new UiSelector().containerSelector(container).patternSelector(pattern));
+    }
+
+    /**
+     * Set the search criteria to match the visible text displayed
+     * in a widget (for example, the text label to launch an app).
+     *
+     * The text for the element must match exactly with the string in your input
+     * argument. Matching is case-sensitive.
+     *
+     * @param text Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector text(String text) {
+        return buildSelector(SELECTOR_TEXT, text);
+    }
+
+    /**
+     * Set the search criteria to match the visible text displayed in a layout
+     * element, using a regular expression.
+     *
+     * The text in the widget must match exactly with the string in your
+     * input argument.
+     *
+     * @param regex a regular expression
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public UiSelector textMatches(String regex) {
+        return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex));
+    }
+
+    /**
+     * Set the search criteria to match visible text in a widget that is
+     * prefixed by the text parameter.
+     *
+     * The matching is case-insensitive.
+     *
+     * @param text Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector textStartsWith(String text) {
+        return buildSelector(SELECTOR_START_TEXT, text);
+    }
+
+    /**
+     * Set the search criteria to match the visible text in a widget
+     * where the visible text must contain the string in your input argument.
+     *
+     * The matching is case-sensitive.
+     *
+     * @param text Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector textContains(String text) {
+        return buildSelector(SELECTOR_CONTAINS_TEXT, text);
+    }
+
+    /**
+     * Set the search criteria to match the class property
+     * for a widget (for example, "android.widget.Button").
+     *
+     * @param className Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector className(String className) {
+        return buildSelector(SELECTOR_CLASS, className);
+    }
+
+    /**
+     * Set the search criteria to match the class property
+     * for a widget, using a regular expression.
+     *
+     * @param regex a regular expression
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public UiSelector classNameMatches(String regex) {
+        return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex));
+    }
+
+    /**
+     * Set the search criteria to match the class property
+     * for a widget (for example, "android.widget.Button").
+     *
+     * @param type type
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public <T> UiSelector className(Class<T> type) {
+        return buildSelector(SELECTOR_CLASS, type.getName());
+    }
+
+    /**
+     * Set the search criteria to match the content-description
+     * property for a widget.
+     *
+     * The content-description is typically used
+     * by the Android Accessibility framework to
+     * provide an audio prompt for the widget when
+     * the widget is selected. The content-description
+     * for the widget must match exactly
+     * with the string in your input argument.
+     *
+     * Matching is case-sensitive.
+     *
+     * @param desc Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector description(String desc) {
+        return buildSelector(SELECTOR_DESCRIPTION, desc);
+    }
+
+    /**
+     * Set the search criteria to match the content-description
+     * property for a widget.
+     *
+     * The content-description is typically used
+     * by the Android Accessibility framework to
+     * provide an audio prompt for the widget when
+     * the widget is selected. The content-description
+     * for the widget must match exactly
+     * with the string in your input argument.
+     *
+     * @param regex a regular expression
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public UiSelector descriptionMatches(String regex) {
+        return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex));
+    }
+
+    /**
+     * Set the search criteria to match the content-description
+     * property for a widget.
+     *
+     * The content-description is typically used
+     * by the Android Accessibility framework to
+     * provide an audio prompt for the widget when
+     * the widget is selected. The content-description
+     * for the widget must start
+     * with the string in your input argument.
+     *
+     * Matching is case-insensitive.
+     *
+     * @param desc Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector descriptionStartsWith(String desc) {
+        return buildSelector(SELECTOR_START_DESCRIPTION, desc);
+    }
+
+    /**
+     * Set the search criteria to match the content-description
+     * property for a widget.
+     *
+     * The content-description is typically used
+     * by the Android Accessibility framework to
+     * provide an audio prompt for the widget when
+     * the widget is selected. The content-description
+     * for the widget must contain
+     * the string in your input argument.
+     *
+     * Matching is case-insensitive.
+     *
+     * @param desc Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector descriptionContains(String desc) {
+        return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc);
+    }
+
+    /**
+     * Set the search criteria to match the given resource ID.
+     *
+     * @param id Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 18
+     */
+    public UiSelector resourceId(String id) {
+        return buildSelector(SELECTOR_RESOURCE_ID, id);
+    }
+
+    /**
+     * Set the search criteria to match the resource ID
+     * of the widget, using a regular expression.http://blog.bettersoftwaretesting.com/
+     *
+     * @param regex a regular expression
+     * @return UiSelector with the specified search criteria
+     * @since API Level 18
+     */
+    public UiSelector resourceIdMatches(String regex) {
+        return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex));
+    }
+
+    /**
+     * Set the search criteria to match the widget by its node
+     * index in the layout hierarchy.
+     *
+     * The index value must be 0 or greater.
+     *
+     * Using the index can be unreliable and should only
+     * be used as a last resort for matching. Instead,
+     * consider using the {@link #instance(int)} method.
+     *
+     * @param index Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector index(final int index) {
+        return buildSelector(SELECTOR_INDEX, index);
+    }
+
+    /**
+     * Set the search criteria to match the
+     * widget by its instance number.
+     *
+     * The instance value must be 0 or greater, where
+     * the first instance is 0.
+     *
+     * For example, to simulate a user click on
+     * the third image that is enabled in a UI screen, you
+     * could specify a a search criteria where the instance is
+     * 2, the {@link #className(String)} matches the image
+     * widget class, and {@link #enabled(boolean)} is true.
+     * The code would look like this:
+     * <code>
+     * new UiSelector().className("android.widget.ImageView")
+     *    .enabled(true).instance(2);
+     * </code>
+     *
+     * @param instance Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector instance(final int instance) {
+        return buildSelector(SELECTOR_INSTANCE, instance);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are enabled.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector enabled(boolean val) {
+        return buildSelector(SELECTOR_ENABLED, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that have focus.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector focused(boolean val) {
+        return buildSelector(SELECTOR_FOCUSED, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are focusable.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector focusable(boolean val) {
+        return buildSelector(SELECTOR_FOCUSABLE, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are scrollable.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector scrollable(boolean val) {
+        return buildSelector(SELECTOR_SCROLLABLE, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that
+     * are currently selected.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector selected(boolean val) {
+        return buildSelector(SELECTOR_SELECTED, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that
+     * are currently checked (usually for checkboxes).
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector checked(boolean val) {
+        return buildSelector(SELECTOR_CHECKED, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are clickable.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector clickable(boolean val) {
+        return buildSelector(SELECTOR_CLICKABLE, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are checkable.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 18
+     */
+    public UiSelector checkable(boolean val) {
+        return buildSelector(SELECTOR_CHECKABLE, val);
+    }
+
+    /**
+     * Set the search criteria to match widgets that are long-clickable.
+     *
+     * Typically, using this search criteria alone is not useful.
+     * You should also include additional criteria, such as text,
+     * content-description, or the class name for a widget.
+     *
+     * If no other search criteria is specified, and there is more
+     * than one matching widget, the first widget in the tree
+     * is selected.
+     *
+     * @param val Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public UiSelector longClickable(boolean val) {
+        return buildSelector(SELECTOR_LONG_CLICKABLE, val);
+    }
+
+    /**
+     * Adds a child UiSelector criteria to this selector.
+     *
+     * Use this selector to narrow the search scope to
+     * child widgets under a specific parent widget.
+     *
+     * @param selector
+     * @return UiSelector with this added search criterion
+     * @since API Level 16
+     */
+    public UiSelector childSelector(UiSelector selector) {
+        return buildSelector(SELECTOR_CHILD, selector);
+    }
+
+    private UiSelector patternSelector(UiSelector selector) {
+        return buildSelector(SELECTOR_PATTERN, selector);
+    }
+
+    private UiSelector containerSelector(UiSelector selector) {
+        return buildSelector(SELECTOR_CONTAINER, selector);
+    }
+
+    /**
+     * Adds a child UiSelector criteria to this selector which is used to
+     * start search from the parent widget.
+     *
+     * Use this selector to narrow the search scope to
+     * sibling widgets as well all child widgets under a parent.
+     *
+     * @param selector
+     * @return UiSelector with this added search criterion
+     * @since API Level 16
+     */
+    public UiSelector fromParent(UiSelector selector) {
+        return buildSelector(SELECTOR_PARENT, selector);
+    }
+
+    /**
+     * Set the search criteria to match the package name
+     * of the application that contains the widget.
+     *
+     * @param name Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 16
+     */
+    public UiSelector packageName(String name) {
+        return buildSelector(SELECTOR_PACKAGE_NAME, name);
+    }
+
+    /**
+     * Set the search criteria to match the package name
+     * of the application that contains the widget.
+     *
+     * @param regex a regular expression
+     * @return UiSelector with the specified search criteria
+     * @since API Level 17
+     */
+    public UiSelector packageNameMatches(String regex) {
+        return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex));
+    }
+
+    /**
+     * Building a UiSelector always returns a new UiSelector and never modifies the
+     * existing UiSelector being used.
+     */
+    private UiSelector buildSelector(int selectorId, Object selectorValue) {
+        UiSelector selector = new UiSelector(this);
+        if (selectorId == SELECTOR_CHILD || selectorId == SELECTOR_PARENT)
+            selector.getLastSubSelector().mSelectorAttributes.put(selectorId, selectorValue);
+        else
+            selector.mSelectorAttributes.put(selectorId, selectorValue);
+        return selector;
+    }
+
+    /**
+     * Selectors may have a hierarchy defined by specifying child nodes to be matched.
+     * It is not necessary that every selector have more than one level. A selector
+     * can also be a single level referencing only one node. In such cases the return
+     * it null.
+     *
+     * @return a child selector if one exists. Else null if this selector does not
+     * reference child node.
+     */
+    UiSelector getChildSelector() {
+        UiSelector selector = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD, null);
+        if (selector != null)
+            return new UiSelector(selector);
+        return null;
+    }
+
+    UiSelector getPatternSelector() {
+        UiSelector selector =
+                (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PATTERN, null);
+        if (selector != null)
+            return new UiSelector(selector);
+        return null;
+    }
+
+    UiSelector getContainerSelector() {
+        UiSelector selector =
+                (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CONTAINER, null);
+        if (selector != null)
+            return new UiSelector(selector);
+        return null;
+    }
+
+    UiSelector getParentSelector() {
+        UiSelector selector =
+                (UiSelector) mSelectorAttributes.get(UiSelector.SELECTOR_PARENT, null);
+        if (selector != null)
+            return new UiSelector(selector);
+        return null;
+    }
+
+    int getInstance() {
+        return getInt(UiSelector.SELECTOR_INSTANCE);
+    }
+
+    String getString(int criterion) {
+        return (String) mSelectorAttributes.get(criterion, null);
+    }
+
+    boolean getBoolean(int criterion) {
+        return (Boolean) mSelectorAttributes.get(criterion, false);
+    }
+
+    int getInt(int criterion) {
+        return (Integer) mSelectorAttributes.get(criterion, 0);
+    }
+
+    Pattern getPattern(int criterion) {
+        return (Pattern) mSelectorAttributes.get(criterion, null);
+    }
+
+    boolean isMatchFor(AccessibilityNodeInfo node, int index) {
+        int size = mSelectorAttributes.size();
+        for(int x = 0; x < size; x++) {
+            CharSequence s = null;
+            int criterion = mSelectorAttributes.keyAt(x);
+            switch(criterion) {
+            case UiSelector.SELECTOR_INDEX:
+                if (index != this.getInt(criterion))
+                    return false;
+                break;
+            case UiSelector.SELECTOR_CHECKED:
+                if (node.isChecked() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CLASS:
+                s = node.getClassName();
+                if (s == null || !s.toString().contentEquals(getString(criterion))) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CLASS_REGEX:
+                s = node.getClassName();
+                if (s == null || !getPattern(criterion).matcher(s).matches()) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CLICKABLE:
+                if (node.isClickable() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CHECKABLE:
+                if (node.isCheckable() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_LONG_CLICKABLE:
+                if (node.isLongClickable() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CONTAINS_DESCRIPTION:
+                s = node.getContentDescription();
+                if (s == null || !s.toString().toLowerCase()
+                        .contains(getString(criterion).toLowerCase())) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_START_DESCRIPTION:
+                s = node.getContentDescription();
+                if (s == null || !s.toString().toLowerCase()
+                        .startsWith(getString(criterion).toLowerCase())) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_DESCRIPTION:
+                s = node.getContentDescription();
+                if (s == null || !s.toString().contentEquals(getString(criterion))) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_DESCRIPTION_REGEX:
+                s = node.getContentDescription();
+                if (s == null || !getPattern(criterion).matcher(s).matches()) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_CONTAINS_TEXT:
+                s = node.getText();
+                if (s == null || !s.toString().toLowerCase()
+                        .contains(getString(criterion).toLowerCase())) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_START_TEXT:
+                s = node.getText();
+                if (s == null || !s.toString().toLowerCase()
+                        .startsWith(getString(criterion).toLowerCase())) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_TEXT:
+                s = node.getText();
+                if (s == null || !s.toString().contentEquals(getString(criterion))) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_TEXT_REGEX:
+                s = node.getText();
+                if (s == null || !getPattern(criterion).matcher(s).matches()) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_ENABLED:
+                if (node.isEnabled() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_FOCUSABLE:
+                if (node.isFocusable() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_FOCUSED:
+                if (node.isFocused() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_ID:
+                break; //TODO: do we need this for AccessibilityNodeInfo.id?
+            case UiSelector.SELECTOR_PACKAGE_NAME:
+                s = node.getPackageName();
+                if (s == null || !s.toString().contentEquals(getString(criterion))) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_PACKAGE_NAME_REGEX:
+                s = node.getPackageName();
+                if (s == null || !getPattern(criterion).matcher(s).matches()) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_SCROLLABLE:
+                if (node.isScrollable() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_SELECTED:
+                if (node.isSelected() != getBoolean(criterion)) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_RESOURCE_ID:
+                s = node.getViewIdResourceName();
+                if (s == null || !s.toString().contentEquals(getString(criterion))) {
+                    return false;
+                }
+                break;
+            case UiSelector.SELECTOR_RESOURCE_ID_REGEX:
+                s = node.getViewIdResourceName();
+                if (s == null || !getPattern(criterion).matcher(s).matches()) {
+                    return false;
+                }
+                break;
+            }
+        }
+        return matchOrUpdateInstance();
+    }
+
+    private boolean matchOrUpdateInstance() {
+        int currentSelectorCounter = 0;
+        int currentSelectorInstance = 0;
+
+        // matched attributes - now check for matching instance number
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_INSTANCE) >= 0) {
+            currentSelectorInstance =
+                    (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_INSTANCE);
+        }
+
+        // instance is required. Add count if not already counting
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_COUNT) >= 0) {
+            currentSelectorCounter = (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_COUNT);
+        }
+
+        // Verify
+        if (currentSelectorInstance == currentSelectorCounter) {
+            return true;
+        }
+        // Update count
+        if (currentSelectorInstance > currentSelectorCounter) {
+            mSelectorAttributes.put(UiSelector.SELECTOR_COUNT, ++currentSelectorCounter);
+        }
+        return false;
+    }
+
+    /**
+     * Leaf selector indicates no more child or parent selectors
+     * are declared in the this selector.
+     * @return true if is leaf.
+     */
+    boolean isLeaf() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0 &&
+                mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
+            return true;
+        }
+        return false;
+    }
+
+    boolean hasChildSelector() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean hasPatternSelector() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean hasContainerSelector() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean hasParentSelector() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the deepest selector in the chain of possible sub selectors.
+     * A chain of selector is created when either of {@link UiSelector#childSelector(UiSelector)}
+     * or {@link UiSelector#fromParent(UiSelector)} are used once or more in the construction of
+     * a selector.
+     * @return last UiSelector in chain
+     */
+    private UiSelector getLastSubSelector() {
+        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0) {
+            UiSelector child = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD);
+            if (child.getLastSubSelector() == null) {
+                return child;
+            }
+            return child.getLastSubSelector();
+        } else if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0) {
+            UiSelector parent = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PARENT);
+            if (parent.getLastSubSelector() == null) {
+                return parent;
+            }
+            return parent.getLastSubSelector();
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return dumpToString(true);
+    }
+
+    String dumpToString(boolean all) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(UiSelector.class.getSimpleName() + "[");
+        final int criterionCount = mSelectorAttributes.size();
+        for (int i = 0; i < criterionCount; i++) {
+            if (i > 0) {
+                builder.append(", ");
+            }
+            final int criterion = mSelectorAttributes.keyAt(i);
+            switch (criterion) {
+            case SELECTOR_TEXT:
+                builder.append("TEXT=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_TEXT_REGEX:
+                builder.append("TEXT_REGEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_START_TEXT:
+                builder.append("START_TEXT=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CONTAINS_TEXT:
+                builder.append("CONTAINS_TEXT=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CLASS:
+                builder.append("CLASS=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CLASS_REGEX:
+                builder.append("CLASS_REGEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_DESCRIPTION:
+                builder.append("DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_DESCRIPTION_REGEX:
+                builder.append("DESCRIPTION_REGEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_START_DESCRIPTION:
+                builder.append("START_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CONTAINS_DESCRIPTION:
+                builder.append("CONTAINS_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_INDEX:
+                builder.append("INDEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_INSTANCE:
+                builder.append("INSTANCE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_ENABLED:
+                builder.append("ENABLED=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_FOCUSED:
+                builder.append("FOCUSED=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_FOCUSABLE:
+                builder.append("FOCUSABLE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_SCROLLABLE:
+                builder.append("SCROLLABLE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CLICKABLE:
+                builder.append("CLICKABLE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CHECKABLE:
+                builder.append("CHECKABLE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_LONG_CLICKABLE:
+                builder.append("LONG_CLICKABLE=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CHECKED:
+                builder.append("CHECKED=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_SELECTED:
+                builder.append("SELECTED=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_ID:
+                builder.append("ID=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_CHILD:
+                if (all)
+                    builder.append("CHILD=").append(mSelectorAttributes.valueAt(i));
+                else
+                    builder.append("CHILD[..]");
+                break;
+            case SELECTOR_PATTERN:
+                if (all)
+                    builder.append("PATTERN=").append(mSelectorAttributes.valueAt(i));
+                else
+                    builder.append("PATTERN[..]");
+                break;
+            case SELECTOR_CONTAINER:
+                if (all)
+                    builder.append("CONTAINER=").append(mSelectorAttributes.valueAt(i));
+                else
+                    builder.append("CONTAINER[..]");
+                break;
+            case SELECTOR_PARENT:
+                if (all)
+                    builder.append("PARENT=").append(mSelectorAttributes.valueAt(i));
+                else
+                    builder.append("PARENT[..]");
+                break;
+            case SELECTOR_COUNT:
+                builder.append("COUNT=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_PACKAGE_NAME:
+                builder.append("PACKAGE NAME=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_PACKAGE_NAME_REGEX:
+                builder.append("PACKAGE_NAME_REGEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_RESOURCE_ID:
+                builder.append("RESOURCE_ID=").append(mSelectorAttributes.valueAt(i));
+                break;
+            case SELECTOR_RESOURCE_ID_REGEX:
+                builder.append("RESOURCE_ID_REGEX=").append(mSelectorAttributes.valueAt(i));
+                break;
+            default:
+                builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i));
+            }
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/library/core-src/com/android/uiautomator/core/UiWatcher.java b/library/core-src/com/android/uiautomator/core/UiWatcher.java
new file mode 100644
index 0000000..5403e30
--- /dev/null
+++ b/library/core-src/com/android/uiautomator/core/UiWatcher.java
@@ -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();
+}
diff --git a/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
new file mode 100644
index 0000000..1afa513
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -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.isScreenOn();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error getting screen status", e);
+            throw new RuntimeException(e);
+        }
+        return ret;
+    }
+}
diff --git a/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
new file mode 100644
index 0000000..1fa9bac
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -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;
+        }
+    }
+}
diff --git a/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java b/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
new file mode 100644
index 0000000..f0c60d2
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
@@ -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);
+
+}
diff --git a/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java b/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
new file mode 100644
index 0000000..cda49f6
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
@@ -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);
+    }
+}
diff --git a/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
new file mode 100644
index 0000000..e7d961b
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -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.
+    }
+}
diff --git a/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
new file mode 100644
index 0000000..1de5a4d
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
@@ -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);
+    }
+
+}
diff --git a/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
new file mode 100644
index 0000000..ef167f9
--- /dev/null
+++ b/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -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);
+    }
+}
diff --git a/samples/SkeletonTest/Android.mk b/samples/SkeletonTest/Android.mk
new file mode 100644
index 0000000..a4a776f
--- /dev/null
+++ b/samples/SkeletonTest/Android.mk
@@ -0,0 +1,30 @@
+#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_target_dir := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := uiautomator.skeletontest
+
+LOCAL_JAVA_LIBRARIES := uiautomator.core
+
+LOCAL_MODULE_PATH := $(local_target_dir)
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/samples/SkeletonTest/README b/samples/SkeletonTest/README
new file mode 100644
index 0000000..b402039
--- /dev/null
+++ b/samples/SkeletonTest/README
@@ -0,0 +1,19 @@
+This is an (almost) empty test, it serves as a prototype to create new UI
+Automator tests that can be compiled inside the Android build tree. The single
+test case included performs a key press, and send out some information about
+the test device.
+
+Steps to run this test:
+* have a fully built Android source tree
+* build the test:
+  mmm frameworks/testing/uiautomator/samples/SkeletonTest
+* deploy the test:
+  adb push ${OUT}/data/local/tmp/uiautomator.skeletontest.jar /data/local/tmp/
+* run the test:
+  adb shell uiautomator runtest uiautomator.skeletontest.jar \
+    -e class com.android.uiautomator.samples.skeleton.DemoTestCase
+
+Steps to create new tests off it:
+* cp -r frameworks/testing/uiautomator/samples/SkeletonTest /new/location
+* modify Android.mk, replace LOCAL_MODULE_NAME, change LOCAL_MODULE_TAGS if
+  necessary, add new dependecies if needed
diff --git a/samples/SkeletonTest/src/com/android/uiautomator/samples/skeleton/DemoTestCase.java b/samples/SkeletonTest/src/com/android/uiautomator/samples/skeleton/DemoTestCase.java
new file mode 100644
index 0000000..1abc25a
--- /dev/null
+++ b/samples/SkeletonTest/src/com/android/uiautomator/samples/skeleton/DemoTestCase.java
@@ -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.samples.skeleton;
+
+import android.app.Activity;
+import android.graphics.Point;
+import android.os.Bundle;
+
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+public class DemoTestCase extends UiAutomatorTestCase {
+
+    public void testDemo() {
+        assertTrue(getUiDevice().pressHome());
+        Bundle status = new Bundle();
+        status.putString("msg", "This is a demo test and I just pressed HOME");
+        status.putString("product", getUiDevice().getProductName());
+        Point p = getUiDevice().getDisplaySizeDp();
+        status.putInt("dp-width", p.x);
+        status.putInt("dp-height", p.y);
+        getAutomationSupport().sendStatus(Activity.RESULT_OK, status);
+    }
+}
diff --git a/uiautomator_test_libraries/Android.mk b/uiautomator_test_libraries/Android.mk
new file mode 100644
index 0000000..f975016
--- /dev/null
+++ b/uiautomator_test_libraries/Android.mk
@@ -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_target_dir := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := com.android.uiautomator.platform.common
+
+LOCAL_SDK_VERSION := 16
+
+LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v$(LOCAL_SDK_VERSION)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(local_target_dir)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java
new file mode 100644
index 0000000..dd1bb5b
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java
@@ -0,0 +1,163 @@
+/*
+ * 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.common;
+
+import android.util.Log;
+
+import com.android.uiautomator.core.UiDevice;
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.core.UiWatcher;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class UiWatchers {
+    private static final String LOG_TAG = UiWatchers.class.getSimpleName();
+    private final List<String> mErrors = new ArrayList<String>();
+
+    /**
+     * We can use the UiDevice registerWatcher to register a small script to be executed when the
+     * framework is waiting for a control to appear. Waiting may be the cause of an unexpected
+     * dialog on the screen and it is the time when the framework runs the registered watchers.
+     * This is a sample watcher looking for ANR and crashes. it closes it and moves on. You should
+     * create your own watchers and handle error logging properly for your type of tests.
+     */
+    public void registerAnrAndCrashWatchers() {
+
+        UiDevice.getInstance().registerWatcher("ANR", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().className(
+                        "com.android.server.am.AppNotRespondingDialog"));
+                String errorText = null;
+                if (window.exists()) {
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onAnrDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        // class names may have changed
+        UiDevice.getInstance().registerWatcher("ANR2", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().packageName("android")
+                        .textContains("isn't responding."));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onAnrDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        UiDevice.getInstance().registerWatcher("CRASH", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().className(
+                        "com.android.server.am.AppErrorDialog"));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onCrashDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        UiDevice.getInstance().registerWatcher("CRASH2", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().packageName("android")
+                        .textContains("has stopped"));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onCrashDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        Log.i(LOG_TAG, "Registed GUI Exception watchers");
+    }
+
+    public void onAnrDetected(String errorText) {
+        mErrors.add(errorText);
+    }
+
+    public void onCrashDetected(String errorText) {
+        mErrors.add(errorText);
+    }
+
+    public void reset() {
+        mErrors.clear();
+    }
+
+    public List<String> getErrors() {
+        return Collections.unmodifiableList(mErrors);
+    }
+
+    /**
+     * Current implementation ignores the exception and continues.
+     */
+    public void postHandler() {
+        // TODO: Add custom error logging here
+
+        String formatedOutput = String.format("UI Exception Message: %-20s\n", UiDevice
+                .getInstance().getCurrentPackageName());
+        Log.e(LOG_TAG, formatedOutput);
+
+        UiObject buttonOK = new UiObject(new UiSelector().text("OK").enabled(true));
+        // sometimes it takes a while for the OK button to become enabled
+        buttonOK.waitForExists(5000);
+        try {
+            buttonOK.click();
+        } catch (UiObjectNotFoundException e) {
+            Log.e(LOG_TAG, "Exception", e);
+        }
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java
new file mode 100644
index 0000000..e3d9b61
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java
@@ -0,0 +1,96 @@
+/*
+ * 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.common.helpers;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * Base app helper class intended for all app helper to extend.
+ * This class provides common APIs that are expected to be present across
+ * all app helpers.
+ */
+public abstract class AppHelperBase {
+
+    /*
+     * App helpers should provide methods for accessing various UI widgets.
+     * Assume the app has an Action Bar, the helper should provide something similar to
+     * SomeAppHelper.ActionBar.getRefreshButton(). Methods like this help the tests check the
+     * state of the targeted widget as well as clicking it if need be. These types of methods are
+     * referred to as object getters. If there are different components, consider creating internal
+     * name spaces as in the .ActionBar example for better context.
+     *
+     * Adding basic units of functionality APIs is also very helpful to test.
+     * Consider the Alarm clock application as an example. It would be helpful if its helper
+     * provided basic functionality such as, setAlarm(Date) and deleteAlarm(Date). Such basic
+     * and key functionality helper methods, will abstract the tests from the UI implementation and
+     * make tests more reliable.
+     */
+
+    /**
+     * Launches the application.
+     *
+     * This is typically performed by executing a shell command to launch the application
+     * via Intent. It is possible to launch the application by automating the Launcher
+     * views and finding the target app icon to launch, however, this is prone to failure if
+     * the Launcher UI implementation differ from one platform to another.
+     */
+    abstract public void open();
+
+    /**
+     * Checks if the application is in foreground.
+     *
+     * This is typically performed by verifying the current package name of the foreground
+     * application. See UiDevice.getCurrentPackageName()
+     * @return true if open, else false.
+     */
+    abstract public boolean isOpen();
+
+
+    /**
+     * Helper to execute a shell command.
+     * @param command
+     */
+    protected void runShellCommand(String command) {
+        Process p = null;
+        BufferedReader resultReader = null;
+        try {
+            p = Runtime.getRuntime().exec(command);
+            int status = p.waitFor();
+            if (status != 0) {
+                System.err.println(String.format("Run shell command: %s, status: %s", command,
+                        status));
+            }
+        } catch (IOException e) {
+            System.err.println("// Exception from command " + command + ":");
+            System.err.println(e.toString());
+        } catch (InterruptedException e) {
+            System.err.println("// Interrupted while waiting for the command to finish. ");
+            System.err.println(e.toString());
+        } finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                System.err.println(e.toString());
+            }
+        }
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/DatePickerHelper.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/DatePickerHelper.java
new file mode 100644
index 0000000..6c7db3a
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/DatePickerHelper.java
@@ -0,0 +1,195 @@
+/*
+ * 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.common.helpers;
+
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+
+import java.util.Calendar;
+
+/**
+ * Use this helper anywhere there is a date picker to manage. This helper
+ * will set date specified in a Calendar object.
+ */
+public class DatePickerHelper {
+
+    public static final int MONTH = 0;
+    public static final int DAY = 1;
+    public static final int YEAR = 2;
+
+    public static String getCurrentMonth() throws UiObjectNotFoundException {
+        return getNumberPickerField(MONTH).getText();
+    }
+
+    public static String getCurrentDay() throws UiObjectNotFoundException {
+        return getNumberPickerField(DAY).getText();
+    }
+
+    public static String getCurrentYear() throws UiObjectNotFoundException {
+        return getNumberPickerField(YEAR).getText();
+    }
+
+    public static void incrementMonth() throws UiObjectNotFoundException {
+        incrementMonth(1);
+    }
+
+    public static void incrementMonth(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerIncrementButton(MONTH).click();
+    }
+
+    public static void decrementMonth() throws UiObjectNotFoundException {
+        decrementMonth(1);
+    }
+
+    public static void decrementMonth(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerDecrementButton(MONTH).click();
+    }
+
+    public static void incrementDay() throws UiObjectNotFoundException {
+        incrementDay(1);
+    }
+
+    public static void incrementDay(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerIncrementButton(DAY).click();
+    }
+
+    public static void decrementDay() throws UiObjectNotFoundException {
+        decrementDay(1);
+    }
+
+    public static void decrementDay(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerDecrementButton(DAY).click();
+    }
+
+    public static void incrementYear() throws UiObjectNotFoundException {
+        incrementYear(1);
+    }
+
+    public static void incrementYear(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerIncrementButton(YEAR).click();
+    }
+
+    public static void decrementYear() throws UiObjectNotFoundException {
+        decrementYear(1);
+    }
+
+    public static void decrementYear(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerDecrementButton(YEAR).click();
+    }
+
+    public static UiObject getNumberPicker(int instance) {
+        return new UiObject(new UiSelector().className(
+                android.widget.NumberPicker.class.getName()).instance(instance));
+    }
+
+    public static UiObject getNumberPickerField(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.EditText.class.getName()));
+    }
+
+    public static UiObject getNumberPickerDecrementButton(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.Button.class.getName()).instance(0));
+    }
+
+    public static UiObject getNumberPickerIncrementButton(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.Button.class.getName()).instance(1));
+    }
+
+    public static void clickDone() throws UiObjectNotFoundException {
+        new UiObject(new UiSelector().text("Done")).click();
+    }
+
+    public static void setDate(Calendar cal) throws UiObjectNotFoundException {
+        int calYear = cal.get(Calendar.YEAR);
+        int calMonth = cal.get(Calendar.MONTH);
+        int calDay = cal.get(Calendar.DAY_OF_MONTH);
+
+        // Adjust day - increment or decrement using the shortest path
+        // while accounting for number of days in month and considering
+        // special case for Feb and leap years.
+        int dpDay = Integer.parseInt(getCurrentDay());
+        if (calDay > dpDay) {
+            if (calDay - dpDay < getDaysInMonth(calYear, calMonth) / 2)
+                incrementDay(calDay - dpDay);
+            else
+                decrementDay(dpDay - calDay + getDaysInMonth(calYear, calMonth));
+        } else if (dpDay > calDay) {
+            if (dpDay - calDay < getDaysInMonth(calYear, calMonth) / 2)
+                decrementDay(dpDay - calDay);
+            else
+                incrementDay(calDay - dpDay + getDaysInMonth(calYear, calMonth));
+        }
+
+        // Adjust month - increment or decrement using the shortest path
+        int dpMonth = toMonthNumber(getCurrentMonth());
+        if (calMonth > dpMonth) {
+            if (calMonth - dpMonth < 6)
+                incrementMonth(calMonth - dpMonth);
+            else
+                decrementMonth(dpMonth - calMonth + 12);
+        } else if (dpMonth > calMonth) {
+            if (dpMonth - calMonth < 6)
+                decrementMonth(dpMonth - calMonth);
+            else
+                incrementMonth(calMonth - dpMonth + 12);
+        }
+
+        // Adjust year
+        int dpYear = Integer.parseInt(getCurrentYear());
+        if (calYear > dpYear) {
+            incrementYear(calYear - dpYear);
+        } else if (dpYear > calYear) {
+            decrementYear(dpYear - calYear);
+        }
+    }
+
+    private static int toMonthNumber(String monthName) {
+        String months[] = new String[] {"January", "February", "March", "April", "May", "June",
+                "July", "August", "September", "October", "November", "December"};
+
+        for (int x = 0; x < months.length; x++) {
+            if (months[x].contains(monthName))
+                return x;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Get the number of days in the month
+     * @param year
+     * @param month
+     * @return
+     */
+    private static int getDaysInMonth(int year, int month) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, year);
+        cal.set(Calendar.MONTH, month);
+        return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/TimePickerHelper.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/TimePickerHelper.java
new file mode 100644
index 0000000..c5e3add
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/TimePickerHelper.java
@@ -0,0 +1,162 @@
+/*
+ * 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.common.helpers;
+
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Use this helper anywhere there is a time picker to manage. This helper
+ * will set time specified in a Calendar object.
+ */
+public class TimePickerHelper {
+
+    public static final int HOUR = 0;
+    public static final int MINUTE = 1;
+    public static final int MERIDIEM = 2;
+
+    public static String getCurrentHour() throws UiObjectNotFoundException {
+        return getNumberPickerField(HOUR).getText();
+    }
+
+    public static String getCurrentMinute() throws UiObjectNotFoundException {
+        return getNumberPickerField(MINUTE).getText();
+    }
+
+    public static String getCurrentMeridiem() throws UiObjectNotFoundException {
+        return getNumberPickerField(MERIDIEM).getText();
+    }
+
+
+    public static void incrementHour() throws UiObjectNotFoundException {
+        incrementHour(1);
+    }
+
+    public static void incrementHour(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerIncrementButton(HOUR).click();
+    }
+
+    public static void decrementHour() throws UiObjectNotFoundException {
+        decrementHour(1);
+    }
+
+    public static void decrementHour(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerDecrementButton(HOUR).click();
+    }
+
+    public static void incrementMinute() throws UiObjectNotFoundException {
+        incrementMinute(1);
+    }
+
+    public static void incrementMinute(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerIncrementButton(MINUTE).click();
+    }
+
+    public static void decrementMinute() throws UiObjectNotFoundException {
+        decrementMinute(1);
+    }
+
+    public static void decrementMinute(int count) throws UiObjectNotFoundException {
+        for (int x = 0; x < count; x++)
+            getNumberPickerDecrementButton(MINUTE).click();
+    }
+
+    public static void selectPM() throws UiObjectNotFoundException {
+        getNumberPicker(MERIDIEM).getChild(new UiSelector().text("PM")).click();
+    }
+
+    public static void selectAM() throws UiObjectNotFoundException {
+        getNumberPicker(MERIDIEM).getChild(new UiSelector().text("AM")).click();
+    }
+
+    public static UiObject getNumberPicker(int instance) {
+        return new UiObject(new UiSelector().className(
+                android.widget.NumberPicker.class.getName()).instance(instance));
+    }
+
+    public static UiObject getNumberPickerField(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.EditText.class.getName()));
+    }
+
+    public static UiObject getNumberPickerDecrementButton(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.Button.class.getName()).instance(0));
+    }
+
+    public static UiObject getNumberPickerIncrementButton(int instance)
+            throws UiObjectNotFoundException {
+        return getNumberPicker(instance).getChild(
+                new UiSelector().className(android.widget.Button.class.getName()).instance(1));
+    }
+
+    public static void clickDone() throws UiObjectNotFoundException {
+        new UiObject(new UiSelector().text("Done")).click();
+    }
+
+    public static void setTime(Calendar cal) throws UiObjectNotFoundException {
+        // Adjust minutes - increment or decrement using the shortest path
+        int tpMinute = Integer.parseInt(getCurrentMinute());
+        int calMinute = cal.get(Calendar.MINUTE);
+        if (calMinute > tpMinute) {
+            if (calMinute - tpMinute < 30)
+                incrementMinute(calMinute - tpMinute);
+            else
+                decrementMinute(tpMinute - calMinute + 60);
+        } else if (tpMinute > calMinute) {
+            if (tpMinute - calMinute < 30)
+                decrementMinute(tpMinute - calMinute);
+            else
+                incrementMinute(calMinute - tpMinute + 60);
+        }
+
+        // Adjust hour - increment or decrement using the shortest path
+        int tpHour = Integer.parseInt(getCurrentHour());
+        int calHour = cal.get(Calendar.HOUR);
+        if (calHour > tpHour) {
+            if (calHour - tpHour < 6)
+                incrementHour(calHour - tpHour);
+            else
+                decrementHour(tpHour - calHour + 12);
+        } else if (tpHour > calHour) {
+            if (tpHour - calHour < 6)
+                decrementHour(tpHour - calHour);
+            else
+                incrementHour(calHour - tpHour + 12);
+        }
+
+        // Adjust meridiem
+        String calMer = cal.getDisplayName(Calendar.AM_PM, Calendar.SHORT, Locale.US);
+        String tpMer = getCurrentMeridiem();
+        if (tpMer.equalsIgnoreCase(calMer))
+            return;
+
+        if (!calMer.equalsIgnoreCase("AM")) {
+            selectPM();
+        } else {
+            selectAM();
+        }
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
new file mode 100644
index 0000000..6662f80
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
@@ -0,0 +1,481 @@
+/*
+ * 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.platform;
+
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.uiautomator.core.UiDevice;
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Base class for jank test.
+ * All jank test needs to extend JankTestBase
+ */
+public class JankTestBase extends UiAutomatorTestCase {
+    private static final String TAG = JankTestBase.class.getSimpleName();
+
+    protected UiDevice mDevice;
+    protected TestWatchers mTestWatchers = null;
+    protected BufferedWriter mWriter = null;
+    protected BufferedWriter mStatusWriter = null;
+    protected int mIteration = 20; // default iteration is set 20
+    /* can be used to enable/disable systrace in the test */
+    protected int mTraceTime = 0;
+    protected Bundle mParams;
+    protected String mTestCaseName;
+    protected int mSuccessTestRuns = 0;
+    protected Thread mThread = null;
+
+    // holds all params for the derived tests
+    private static final String PROPERTY_FILE_NAME = "UiJankinessTests.conf";
+    private static final String PARAM_CONFIG = "conf";
+    private static final String LOCAL_TMP_DIR = "/data/local/tmp/";
+    // File that hold the test results
+    private static String OUTPUT_FILE_NAME = LOCAL_TMP_DIR + "UiJankinessTestsOutput.txt";
+    // File that hold test status, e.g successful test iterations
+    private static String STATUS_FILE_NAME = LOCAL_TMP_DIR + "UiJankinessTestsStatus.txt";
+    private static final String RAW_DATA_DIR = LOCAL_TMP_DIR + "UiJankinessRawData";
+
+    private static int SUCCESS_THRESHOLD = 80;
+    private static boolean DEBUG = false;
+
+    /* default animation time is set to 2 seconds */
+    protected static final long DEFAULT_ANIMATION_TIME = 2 * 1000;
+    /* default swipe steps for fling animation */
+    protected static final int DEFAULT_FLING_STEPS = 8;
+
+    /* Array to record jankiness data in each test iteration */
+    private int[] jankinessArray;
+    /* Array to record frame rate in each test iteration */
+    private double[] frameRateArray;
+    /* Array to save max accumulated frame number in each test iteration */
+    private int[] maxDeltaVsyncArray;
+    /* Default file to store the systrace */
+    private static final File SYSTRACE_DIR = new File(LOCAL_TMP_DIR, "systrace");
+    /* Default trace file name */
+    private static final String TRACE_FILE_NAME = "trace.txt";
+    /* Default tracing time is 5 seconds */
+    private static final int DEFAULT_TRACE_TIME = 5; // 5 seconds
+    // Command to dump compressed trace data
+    private static final String ATRACE_COMMAND = "atrace -z -t %d gfx input view sched freq";
+
+    /**
+     * Thread to capture systrace log from the test
+     */
+    public class SystraceTracker implements Runnable {
+        File mFile = new File(SYSTRACE_DIR, TRACE_FILE_NAME);
+        int mTime = DEFAULT_TRACE_TIME;
+
+        public SystraceTracker(int traceTime, String fileName) {
+            try {
+                if (!SYSTRACE_DIR.exists()) {
+                    if (!SYSTRACE_DIR.mkdir()) {
+                        log(String.format("create directory %s failed, you can manually create "
+                                + "it and start the test again", SYSTRACE_DIR.getAbsolutePath()));
+                        return;
+                    }
+                }
+            } catch (SecurityException e) {
+                Log.e(TAG, "creating directory failed?", e);
+            }
+
+            if (traceTime > 0) {
+                mTime = traceTime;
+            }
+            if (fileName != null) {
+                mFile = new File(SYSTRACE_DIR, fileName);
+            }
+        }
+
+        @Override
+        public void run() {
+            String command = String.format(ATRACE_COMMAND, mTime);
+            Log.v(TAG, "command: " + command);
+            Process p = null;
+            InputStream in = null;
+            BufferedOutputStream out = null;
+            try {
+                p = Runtime.getRuntime().exec(command);
+                Log.v(TAG, "write systrace into file: " + mFile.getAbsolutePath());
+                // read bytes from the process output stream as the output is compressed
+                byte[] buffer = new byte[1024];
+                in = p.getInputStream();
+                out = new BufferedOutputStream(new FileOutputStream(mFile));
+                int n;
+                while ((n = in.read(buffer)) != -1) {
+                    out.write(buffer, 0, n);
+                    out.flush();
+                }
+                in.close();
+                out.close();
+                // read error message
+                BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+                String line;
+                while ((line = br.readLine()) != null) {
+                    Log.e(TAG, "Command return errors: " + line);
+                }
+                br.close();
+
+                // Due to limited buffer size for standard input and output stream,
+                // promptly reading from the input stream or output stream to avoid block
+                int status = p.waitFor();
+                if (status != 0) {
+                    Log.e(TAG, String.format("Run shell command: %s, status: %s",
+                            command, status));
+                }
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Exception from command " + command + ":");
+                Log.e(TAG, "Thread interrupted? ", e);
+            } catch (IOException e) {
+                Log.e(TAG, "Open file error: ", e);
+            } catch (IllegalThreadStateException e) {
+                Log.e(TAG, "the process has not exit yet ", e);
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance();
+        mTestWatchers = new TestWatchers(); // extends the common class UiWatchers
+        mTestWatchers.registerAnrAndCrashWatchers();
+
+        mWriter = new BufferedWriter(new FileWriter(new File(OUTPUT_FILE_NAME), true));
+        mStatusWriter = new BufferedWriter(new FileWriter(new File(STATUS_FILE_NAME), true));
+
+        mParams = getParams();
+        if (mParams != null && !mParams.isEmpty()) {
+            log("mParams is not empty, get properties.");
+            String mIterationStr = getPropertyString(mParams, "iteration");
+            if (mIterationStr != null) {
+                mIteration = Integer.valueOf(mIterationStr);
+            }
+            String mTraceTimeStr = getPropertyString(mParams, "tracetime");
+            if (mTraceTimeStr  != null) {
+                mTraceTime = Integer.valueOf(mTraceTimeStr);
+            }
+        }
+        jankinessArray = new int[mIteration];
+        frameRateArray = new double[mIteration];
+        maxDeltaVsyncArray = new int[mIteration];
+        mTestCaseName = this.getName();
+
+        mSuccessTestRuns = 0;
+        mDevice.pressHome();
+    }
+
+    /**
+     * Create a new thread for systrace and start the thread
+     *
+     * @param testCaseName
+     * @param iteration
+     */
+    protected void startTrace(String testCaseName, int iteration) {
+        if (mTraceTime > 0) {
+            String outputFile = String.format("%s_%d_trace", mTestCaseName, iteration);
+            mThread = new Thread(new SystraceTracker(mTraceTime, outputFile));
+            mThread.start();
+        }
+    }
+
+    /**
+     * Wait for the tracing thread to exit
+     */
+    protected void endTrace() {
+        if (mThread != null) {
+            try {
+                mThread.join();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "wait for the trace thread to exit exception:", e);
+            }
+        }
+    }
+
+    /**
+     * Expects a file from the command line via conf param or default following format each on its
+     * own line. <code>
+     *    key=Value
+     *    Browser_URL1=cnn.com
+     *    Browser_URL2=google.com
+     *    Camera_ShutterDelay=1000
+     *    etc...
+     * </code>
+     * @param Bundle params
+     * @param key
+     * @return the value of the property else defaultValue
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    protected String getPropertyString(Bundle params, String key)
+            throws FileNotFoundException, IOException {
+        Properties prop = new Properties();
+        prop.load(new FileInputStream(new File(LOCAL_TMP_DIR,
+                params.getString(PARAM_CONFIG, PROPERTY_FILE_NAME))));
+        String value = prop.getProperty(key);
+        if (value != null && !value.isEmpty())
+            return value;
+        return null;
+    }
+
+    /**
+     * Expects a file from the command line via conf param or default following format each on its
+     * own line. <code>
+     *    key=Value
+     *    Browser_URL1=cnn.com
+     *    Browser_URL2=google.com
+     *    Camera_ShutterDelay=1000
+     *    etc...
+     * </code>
+     * @param Bundle params
+     * @param key
+     * @return the value of the property else defaultValue
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    protected long getPropertyLong(Bundle params, String key)
+            throws FileNotFoundException, IOException {
+        Properties prop = new Properties();
+        prop.load(new FileInputStream(new File(LOCAL_TMP_DIR,
+                params.getString(PARAM_CONFIG, PROPERTY_FILE_NAME))));
+        String value = prop.getProperty(key);
+        if (value != null && !value.trim().isEmpty())
+            return Long.valueOf(value.trim());
+        return 0;
+    }
+
+    /**
+     * Verify the test result by comparing data sample size with expected value
+     * @param expectedDataSize the expected data size
+     */
+    protected boolean validateResults(int expectedDataSize) {
+        int receivedDataSize = SurfaceFlingerHelper.getDataSampleSize();
+        return ((expectedDataSize > 0) && (receivedDataSize >= expectedDataSize));
+    }
+
+    /**
+     * Process the raw data, calculate jankiness, frame rate and max accumulated frames number
+     * @param testCaseName
+     * @param iteration
+     */
+    protected void recordResults(String testCaseName, int iteration) {
+        long refreshPeriod = SurfaceFlingerHelper.getRefreshPeriod();
+        // if the raw directory doesn't exit, create the directory
+        File rawDataDir = new File(RAW_DATA_DIR);
+        try {
+            if (!rawDataDir.exists()) {
+                if (!rawDataDir.mkdir()) {
+                    log(String.format("create directory %s failed, you can manually create " +
+                            "it and start the test again", rawDataDir));
+                }
+            }
+        } catch (SecurityException e) {
+            Log.e(TAG, "create directory failed: ", e);
+        }
+        String rawFileName = String.format("%s/%s_%d.txt", RAW_DATA_DIR, testCaseName, iteration);
+        // write results into a file
+        BufferedWriter fw = null;
+        try {
+            fw = new BufferedWriter(new FileWriter(new File(rawFileName), false));
+            fw.write(SurfaceFlingerHelper.getFrameBufferData());
+        } catch (IOException e) {
+            Log.e(TAG, "failed to write to file", e);
+            return;
+        } finally {
+            try {
+                if (fw != null) {
+                    fw.close();
+                }
+            }
+            catch (IOException e) {
+                    Log.e(TAG, "close file failed.", e);
+            }
+        }
+
+        // get jankiness count
+        int jankinessCount = SurfaceFlingerHelper.getVsyncJankiness();
+        // get frame rate
+        double frameRate = SurfaceFlingerHelper.getFrameRate();
+        // get max accumulated frames
+        int maxDeltaVsync = SurfaceFlingerHelper.getMaxDeltaVsync();
+
+        // only record data when they are valid
+        if (jankinessCount >=0 && frameRate > 0) {
+            jankinessArray[iteration] = jankinessCount;
+            frameRateArray[iteration] = frameRate;
+            maxDeltaVsyncArray[iteration] = maxDeltaVsync;
+            mSuccessTestRuns++;
+        }
+        String msg = String.format("%s, iteration %d\n" +
+                "refresh period: %d\n" +
+                "jankiness count: %d\n" +
+                "frame rate: %f\n" +
+                "max accumulated frames: %d\n",
+                testCaseName, iteration, refreshPeriod,
+                jankinessCount, frameRate, maxDeltaVsync);
+        log(msg);
+        if (DEBUG) {
+            SurfaceFlingerHelper.printData(testCaseName, iteration);
+        }
+    }
+
+    /**
+     * Process data from all test iterations, and save to disk
+     * @param testCaseName
+     */
+    protected void saveResults(String testCaseName) {
+        // write test status into status file
+        try {
+            mStatusWriter.write(String.format("%s: %d success runs out of %d iterations\n",
+                    testCaseName, mSuccessTestRuns, mIteration));
+        } catch (IOException e) {
+            log("failed to write output for test case " + testCaseName);
+        }
+
+        // if successful test runs is less than the threshold, no results will be saved.
+        if (mSuccessTestRuns * 100 / mIteration < SUCCESS_THRESHOLD) {
+            log(String.format("In %s, # of successful test runs out of %s iterations: %d ",
+                    testCaseName, mIteration, mSuccessTestRuns));
+            log(String.format("threshold is %d%%", SUCCESS_THRESHOLD));
+            return;
+        }
+
+        if (DEBUG) {
+            print(jankinessArray, "jankiness array");
+            print(frameRateArray, "frame rate array");
+            print(maxDeltaVsyncArray, "max delta vsync array");
+        }
+        double avgJankinessCount = getAverage(jankinessArray);
+        int maxJankinessCount = getMaxValue(jankinessArray);
+        double avgFrameRate = getAverage(frameRateArray);
+        double avgMaxDeltaVsync = getAverage(maxDeltaVsyncArray);
+
+        String avgMsg = String.format("%s\n" +
+                "average number of jankiness: %f\n" +
+                "max number of jankiness: %d\n" +
+                "average frame rate: %f\n" +
+                "average of max accumulated frames: %f\n",
+                testCaseName, avgJankinessCount, maxJankinessCount, avgFrameRate, avgMaxDeltaVsync);
+        log(avgMsg);
+
+        try {
+            mWriter.write(avgMsg);
+        } catch (IOException e) {
+            log("failed to write output for test case " + testCaseName);
+        }
+    }
+
+    // return the max value in an integer array
+    private int getMaxValue(int[] intArray) {
+        int index = 0;
+        int max = intArray[index];
+        for (int i  = 1; i < intArray.length; i++) {
+            if (max < intArray[i]) {
+                max = intArray[i];
+            }
+        }
+        return max;
+    }
+
+    private double getAverage(int[] intArray) {
+        int mean = 0;
+        int numberTests = 0;
+        for (int i = 0; i < intArray.length; i++) {
+            // in case in some iteration, test fails, no data points is collected
+            if (intArray[i] >= 0) {
+                mean += intArray[i];
+                ++numberTests;
+            }
+        }
+        return (double)mean/numberTests;
+    }
+
+    private double getAverage(double[] doubleArray) {
+        double mean = 0;
+        int numberTests = 0;
+        for (int i = 0; i < doubleArray.length; i++) {
+            // in case in some iteration, test fails, no data points is collected
+            if (doubleArray[i] >= 0) {
+                mean += doubleArray[i];
+                ++numberTests;
+            }
+        }
+        return mean/numberTests;
+    }
+
+    private void print(int[] intArray, String arrayName) {
+        log("start to print array for " + arrayName);
+        for (int i = 0; i < intArray.length; i++) {
+            log(String.format("%d: %d", i, intArray[i]));
+        }
+    }
+
+    private void print(double[] doubleArray, String arrayName) {
+        log("start to print array for " + arrayName);
+        for (int i = 0; i < doubleArray.length; i++) {
+            log(String.format("%d: %f", i, doubleArray[i]));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mWriter != null) {
+            mWriter.close();
+        }
+        if (mStatusWriter != null) {
+            mStatusWriter.close();
+        }
+    }
+
+   private void log(String message) {
+       Log.v(TAG, message);
+   }
+
+   /**
+    * Set the total number of test iteration
+    * @param iteration
+    */
+   protected void setIteration(int iteration){
+       mIteration = iteration;
+   }
+
+   /**
+    * Get the total number of test iteration
+    * @return iteration
+    */
+   protected int getIteration(){
+       return mIteration;
+   }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
new file mode 100644
index 0000000..748b433
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
@@ -0,0 +1,421 @@
+/*
+ * 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.platform;
+
+import android.os.Environment;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.lang.Math;
+
+/*
+ * Tools to measure jankiness through SurfaceFlinger
+ */
+public class SurfaceFlingerHelper {
+    private static String TAG = "SurfaceFlingerHelper";
+    private static int BUFFER_SIZE = 128;
+    private static int BUFFER_NUMBER = 3;
+    private static String CLEAR_BUFFER_CMD = "dumpsys SurfaceFlinger --latency-clear";
+    private static String FRAME_LATENCY_CMD = "dumpsys SurfaceFlinger --latency";
+    private static final String RAW_DATA_DIR = "UiJankinessRawData";
+    private static final String LOCAL_TMP_DIR = "/data/local/tmp/";
+    /* If the latency between two frames is greater than this number, it it treated as a pause
+     * not a jankiness */
+    private static final int PAUSE_LATENCY = 20;
+
+    /* An array list which includes the raw buffer information from frame latency tool */
+    private static List<List<String>> mFrameBufferData = new ArrayList<List<String>>(BUFFER_SIZE);
+
+    /* Record the refresh period returned from driver */
+    private static long mRefreshPeriod = -1;
+
+    /* Record the size of frame latency */
+    private static int mFrameLatencySampleSize = 0;
+
+    /* An integer array which includes delta vsync */
+    private static long[] mDeltaVsync = new long[BUFFER_SIZE];
+
+    /* Integer array for delta of delta vsync */
+    private static long[] mDelta2Vsync = new long[BUFFER_SIZE];
+
+    /* the maximum delta vsync number */
+    private static long mMaxDeltaVsync;
+
+    /* Normalized data */
+    private static double[] mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
+    private static int[] mRoundNormalizedDelta2Vsync = new int[BUFFER_SIZE];
+    // Symbol of unfinished frame time */
+    public static final String PENDING_FENCE_TIME = new Long(Long.MAX_VALUE).toString();
+
+    /**
+     * Run clear buffer command and clear the saved frame buffer results
+     *
+     * @param windowName the window name that the buffer will be cleared
+     */
+    public static void clearBuffer(String windowName) {
+        // clear results
+        if (mFrameBufferData != null) {
+            mFrameBufferData.clear();
+        }
+        Arrays.fill(mDeltaVsync, -1);
+        Arrays.fill(mDelta2Vsync, -1);
+        Arrays.fill(mNormalizedDelta2Vsync, -1.0);
+        Arrays.fill(mRoundNormalizedDelta2Vsync, -1);
+        mRefreshPeriod = -1;
+        mFrameLatencySampleSize = 0;
+        mMaxDeltaVsync = 0;
+
+        Process p = null;
+        BufferedReader resultReader = null;
+        String command = CLEAR_BUFFER_CMD;
+        if (windowName != null) {
+            command = String.format("%s %s", CLEAR_BUFFER_CMD, windowName);
+        }
+        try {
+            p = Runtime.getRuntime().exec(command);
+            int status = p.waitFor();
+            if (status != 0) {
+                Log.e(TAG, String.format("Run shell command: %s, status: %s",
+                        command, status));
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "// Exception from command " + command + ":", e);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "// Interrupted while waiting for the command to finish. ", e);
+        } finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "exception " + e);
+            }
+        }
+    }
+
+    /**
+     * Run frame latency command without ignoring pending fence time
+     *
+     * @param windowName the window name which SurfaceFlinger will acquire frame time for
+     */
+    public static boolean dumpFrameLatency(String windowName) {
+        return dumpFrameLatency(windowName, false);
+    }
+
+    /**
+     * Run frame latency command to get frame time
+     *
+     * @param windowName the window name which SurfaceFlinger will get frame time for
+     * @param ignorePendingFenceTime flag to process frames with pending fence time
+     *                              set true to ignore pending fence time
+     *                              set false to fail the test if pending fence time is not allowed
+     */
+    public static boolean dumpFrameLatency(String windowName, boolean ignorePendingFenceTime) {
+        Process p = null;
+        BufferedReader resultReader = null;
+        String command = FRAME_LATENCY_CMD;
+        if (windowName != null) {
+            command = String.format("%s %s", FRAME_LATENCY_CMD, windowName);
+        }
+        log("dump frame latency command: " + command);
+        try {
+            p = Runtime.getRuntime().exec(command);
+            int status = p.waitFor();
+            if (status != 0) {
+                Log.e(TAG, String.format("Run shell command: %s, status: %s",command, status));
+            }
+            resultReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+            String line = resultReader.readLine();
+            mRefreshPeriod = Long.parseLong(line.trim());
+            log("reading refresh period: " + mRefreshPeriod);
+            if (mRefreshPeriod < 0) {
+                return false;
+            }
+            boolean dataInvalidFlag = false;
+            while((line = resultReader.readLine()) != null) {
+                // remove lines which are empty
+                if (line.trim().isEmpty()) {
+                    break;
+                }
+                String[] bufferValues = line.split("\\s+");
+                if (bufferValues[0].trim().compareTo("0") == 0) {
+                    continue;
+                } else if (bufferValues[1].trim().compareTo(PENDING_FENCE_TIME) == 0 ) {
+                    if (ignorePendingFenceTime) {
+                        log("ignore pending fence time");
+                        dataInvalidFlag = true;
+                    } else {
+                        log("the data contains unfinished frame time, please allow the animation"
+                            + " to finish in the test before calling dumpFrameLatency.");
+                        return false;
+                    }
+                }
+                // store raw data which could have both valid and invalid data
+                List<String> delayArray = Arrays.asList(bufferValues);
+                mFrameBufferData.add(delayArray);
+                if (!dataInvalidFlag) {
+                    // only count frames which have valid time
+                    ++mFrameLatencySampleSize;
+                }
+            }
+            log("frame latency sample size: " + mFrameLatencySampleSize);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "// Exception from command " + command + ":", e);
+        } catch (IOException e) {
+            Log.e(TAG, "Open file error: ", e);
+            return false;
+        }
+        finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "io exception: ", e);
+            }
+        }
+        return true;
+    }
+
+    public static int getDataSampleSize() {
+        return mFrameLatencySampleSize;
+    }
+
+    public static long getRefreshPeriod() {
+        if (mRefreshPeriod < 0) {
+            // Haven't dump the frame latency yet
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving the refresh period");
+        }
+        return mRefreshPeriod;
+    }
+
+    public static String getFrameBufferData() {
+        if (mFrameBufferData.get(0) == null) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
+            return null;
+        }
+        String rawData = String.format("%d\n", mRefreshPeriod);
+        List<String> tempList = new ArrayList<String>(BUFFER_NUMBER);
+        for (int i = 0; i < mFrameBufferData.size(); i++) {
+            tempList = mFrameBufferData.get(i);
+            for (int j = 0; j < BUFFER_NUMBER; j++) {
+                rawData += String.format("%s", tempList.get(j));
+                if (j < BUFFER_NUMBER - 1) {
+                    rawData += "\t";
+                } else {
+                    rawData += "\n";
+                }
+            }
+        }
+        return rawData;
+    }
+
+    /**
+     * Calculate delta(vsync)
+     * @return
+     */
+    public static long[] getDeltaVsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            return null;
+        }
+        if (mDeltaVsync[0] < 0 ) {
+            // keep a record of the max DeltaVsync
+            mMaxDeltaVsync = 0;
+            // get the first frame vsync time
+            long preVsyncTime = Long.parseLong(mFrameBufferData.get(0).get(1));
+            for (int i = 0; i < mFrameLatencySampleSize - 1; i++) {
+                long curVsyncTime = Long.parseLong(mFrameBufferData.get(i + 1).get(1));
+                mDeltaVsync[i] = curVsyncTime - preVsyncTime;
+                preVsyncTime = curVsyncTime;
+                if (mMaxDeltaVsync < mDeltaVsync[i]) {
+                    mMaxDeltaVsync = mDeltaVsync[i];
+                }
+            }
+        }
+        return mDeltaVsync;
+    }
+
+    /**
+     * Calculate difference between delta vsync
+     * @return
+     */
+    public static long[] getDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            return null;
+        }
+        if (mDeltaVsync[0] < 0) {
+            getDeltaVsync();
+        }
+        if (mDelta2Vsync[0] < 0) {
+            int numDeltaVsync = mFrameLatencySampleSize - 1;
+            for (int i = 0; i < numDeltaVsync - 1; i++) {
+                mDelta2Vsync[i] = mDeltaVsync[i + 1] - mDeltaVsync[i];
+            }
+        }
+        return mDelta2Vsync;
+    }
+
+    /**
+     * normalized delta(delta(vsync)) by refresh period
+     * @return
+     */
+    public static double[] getNormalizedDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            return null;
+        }
+        if (mDelta2Vsync[0] < 0) {
+            getDelta2Vsync();
+        }
+        if (mNormalizedDelta2Vsync[0] < 0) {
+            for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
+                mNormalizedDelta2Vsync[i] = (double)mDelta2Vsync[i] /mRefreshPeriod;
+            }
+        }
+        return mNormalizedDelta2Vsync;
+    }
+
+    public static int[] getRoundNormalizedDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
+            return null;
+        }
+        if (mNormalizedDelta2Vsync[0] < 0) {
+            getNormalizedDelta2Vsync();
+        }
+
+        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
+             int value = (int)Math.round(Math.max(mNormalizedDelta2Vsync[i], 0.0));
+             mRoundNormalizedDelta2Vsync[i] = value;
+        }
+        return mRoundNormalizedDelta2Vsync;
+    }
+
+    /*
+     * Get number of jankiness using Vsync time difference
+     */
+    public static int getVsyncJankiness() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
+            return -1;
+        }
+        if (mRoundNormalizedDelta2Vsync[0] < 0) {
+            getRoundNormalizedDelta2Vsync();
+        }
+        int numberJankiness = 0;
+        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
+            int value = mRoundNormalizedDelta2Vsync[i];
+            // ignore the latency which is too long
+            if (value > 0 && value < PAUSE_LATENCY) {
+                numberJankiness++;
+            }
+        }
+        return numberJankiness;
+    }
+
+    /* Track the maximum delta which shows the accumulating time
+     * before animation starts */
+    public static int getMaxDeltaVsync() {
+        return Math.round((float)mMaxDeltaVsync /mRefreshPeriod);
+    }
+
+    /**
+     * Calculate frame rate
+     * @return
+     */
+    public static double getFrameRate() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before calcuating average frame rate");
+            return -1.0;
+        }
+        if (mFrameBufferData.get(0) == null) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
+            return -1.0;
+        }
+        long startTime = Long.parseLong(mFrameBufferData.get(0).get(1));
+        long endTime =  Long.parseLong(mFrameBufferData.get(mFrameLatencySampleSize - 1).get(1));
+        long totalDuration = endTime - startTime;
+        return (double)((mFrameLatencySampleSize - 1) * Math.pow(10, 9))/totalDuration;
+    }
+
+    /**
+     * Print raw data and processed results into file <testcasename_[iteration]_processed.txt>
+     * @param fileName
+     * @param index
+     */
+    public static void printData(String fileName, int index) {
+        String rawAndProcDataFileName = String.format("%s/%s_%d_processed.txt", RAW_DATA_DIR,
+                fileName, index);
+        log("write raw data and process data into file: " + rawAndProcDataFileName);
+        BufferedWriter fw = null;
+        try {
+            fw = new BufferedWriter(new FileWriter(new File(rawAndProcDataFileName), false));
+            // Show the number of jankiness first:
+            fw.write(String.format("Jankiness count: %d\n", getVsyncJankiness()));
+            fw.write(String.format("Max accumulated frames: %d\n", getMaxDeltaVsync()));
+            fw.write(String.format("Frame rate is: %f\n", getFrameRate()));
+
+            // refresh period
+            fw.write(String.valueOf(mRefreshPeriod));
+            fw.write("\n");
+            fw.write("app\tvsync\tset\tdelta(vsync)\tdelta^2(vsync)\t" +
+                    "delta^2(vsync)/refreshPeriod\t normalized delta^2(vsync)\n");
+
+            for (int i = 0; i < mFrameLatencySampleSize; i++) {
+                // write raw data
+                List<String> rawData = mFrameBufferData.get(i);
+                String line = String.format("%s\t%s\t%s\t%d\t%d\t%f\t%d\n",
+                        rawData.get(0), rawData.get(1), rawData.get(2),
+                        mDeltaVsync[i], mDelta2Vsync[i],
+                        mNormalizedDelta2Vsync[i], mRoundNormalizedDelta2Vsync[i]);
+                fw.write(line);
+            }
+        } catch (IOException e) {
+            log("Open file error: " + e.toString());
+        } finally {
+            try {
+                if (fw != null) {
+                    fw.flush();
+                    fw.close();
+                }
+            }
+            catch (IOException e) {
+                Log.e(TAG, "close file exception: ", e);
+            }
+        }
+    }
+
+    private static void log(String msg) {
+        Log.v(TAG, msg);
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java
new file mode 100644
index 0000000..5cab83a
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java
@@ -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.platform;
+
+import com.android.uiautomator.common.UiWatchers;
+
+public class TestWatchers extends UiWatchers {
+    private String TAG = "TestWatchers";
+
+    @Override
+    public void onAnrDetected(String errorText) {
+        // The ANR dialog is still open now and upon returning from here
+        // it will automatically get closed. See UiWatchers or implement
+        // your handlers directly.
+        super.onAnrDetected("ANR:" + errorText);
+    }
+
+    @Override
+    public void onCrashDetected(String errorText) {
+        // what do we need to do here?
+        // The Crash dialog is still open now and upon returning from here
+        // it will automatically get closed. See UiWatchers or implement
+        // your handlers directly.
+        super.onCrashDetected("CRASH:" + errorText);
+    }
+}
diff --git a/utils/Android.mk b/utils/Android.mk
new file mode 100644
index 0000000..c141484
--- /dev/null
+++ b/utils/Android.mk
@@ -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)
diff --git a/utils/DummyIME/Android.mk b/utils/DummyIME/Android.mk
new file mode 100644
index 0000000..c8d9f87
--- /dev/null
+++ b/utils/DummyIME/Android.mk
@@ -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)
diff --git a/utils/DummyIME/AndroidManifest.xml b/utils/DummyIME/AndroidManifest.xml
new file mode 100644
index 0000000..fd17a52
--- /dev/null
+++ b/utils/DummyIME/AndroidManifest.xml
@@ -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>
diff --git a/utils/DummyIME/res/xml/method.xml b/utils/DummyIME/res/xml/method.xml
new file mode 100644
index 0000000..43a330e
--- /dev/null
+++ b/utils/DummyIME/res/xml/method.xml
@@ -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>
\ No newline at end of file
diff --git a/utils/DummyIME/src/com/android/testing/dummyime/DummyIme.java b/utils/DummyIME/src/com/android/testing/dummyime/DummyIme.java
new file mode 100644
index 0000000..7b7a39a
--- /dev/null
+++ b/utils/DummyIME/src/com/android/testing/dummyime/DummyIme.java
@@ -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;
+    }
+}
diff --git a/utils/DummyIME/src/com/android/testing/dummyime/ImePreferences.java b/utils/DummyIME/src/com/android/testing/dummyime/ImePreferences.java
new file mode 100644
index 0000000..41036ab
--- /dev/null
+++ b/utils/DummyIME/src/com/android/testing/dummyime/ImePreferences.java
@@ -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 {
+
+}