Merge "make sure wificonfiguration scan cache doesnt grow unbounded Bug:18703749" into lmp-mr1-dev
diff --git a/cmds/uiautomator/Android.mk b/cmds/uiautomator/Android.mk
new file mode 100644
index 0000000..5391305
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/MODULE_LICENSE_APACHE2 b/cmds/uiautomator/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmds/uiautomator/MODULE_LICENSE_APACHE2
diff --git a/cmds/uiautomator/api/16.txt b/cmds/uiautomator/api/16.txt
new file mode 100644
index 0000000..f3b0eb7
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/api/17.txt b/cmds/uiautomator/api/17.txt
new file mode 100644
index 0000000..a1d80c4
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/api/current.txt b/cmds/uiautomator/api/current.txt
new file mode 100644
index 0000000..7eeecf5
--- /dev/null
+++ b/cmds/uiautomator/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/uiautomator/api/removed.txt b/cmds/uiautomator/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmds/uiautomator/api/removed.txt
diff --git a/cmds/uiautomator/cmds/Android.mk b/cmds/uiautomator/cmds/Android.mk
new file mode 100644
index 0000000..c141484
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/Android.mk b/cmds/uiautomator/cmds/uiautomator/Android.mk
new file mode 100644
index 0000000..5c91b52
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
new file mode 100644
index 0000000..c35f7fc
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
new file mode 100644
index 0000000..ce55f18
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/src/com/android/commands/uiautomator/Launcher.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/Launcher.java
new file mode 100644
index 0000000..bc1d948
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
new file mode 100644
index 0000000..65611ab
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/uiautomator b/cmds/uiautomator/cmds/uiautomator/uiautomator
new file mode 100755
index 0000000..fe2c735
--- /dev/null
+++ b/cmds/uiautomator/cmds/uiautomator/uiautomator
@@ -0,0 +1,123 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Script to start "uiautomator" on the device
+#
+# The script does a couple of things:
+# * Use an alternative dalvik cache when running as non-root. Jar file needs
+#   to be dexopt'd to run in Dalvik. For plain jar files, this is done at first
+#   use. shell user does not have write permission to default system Dalvik
+#   cache so we redirect to an alternative cache
+# * special processing for subcommand 'runtest':
+#    * '--nohup' allows process continue to run even if parent process that
+#      started it has already terminated. We parse for this parameter and set
+#      signal trap. This is useful for testing with USB disconnected
+#    * all jar files that the test classes resides in, or dependent on are
+#      provided on command line and exported to CLASSPATH environment variable
+#      before starting the Java code. This offloads the task of class loading
+#      and resolving of cross jar class dependency to Dalvik
+#    * all other subcommand or options are directly passed into Java code for
+#      further parsing
+
+export run_base=/data/local/tmp
+export base=/system
+
+# if not running as root, trick dalvik into using an alternative dex cache
+if [ ${USER_ID} -ne 0 ]; then
+  tmp_cache=${run_base}/dalvik-cache
+
+  if [ ! -d ${tmp_cache} ]; then
+    mkdir -p ${tmp_cache}
+  fi
+
+  export ANDROID_DATA=${run_base}
+fi
+
+# take first parameter as the command
+cmd=${1}
+
+if [ -z "${1}" ]; then
+  cmd="help"
+fi
+
+# strip the command parameter
+if [ -n "${1}" ]; then
+  shift
+fi
+
+CLASSPATH=/system/framework/android.test.runner.jar:${base}/framework/uiautomator.jar
+
+# eventually args will be what get passed down to Java code
+args=
+# we also pass the list of jar files, so we can extract class names for tests
+# if they are not explicitly specified
+jars=
+
+# special case pre-processing for 'runtest' command
+if [ "${cmd}" == "runtest" ]; then
+  # first parse the jar paths
+  while [ true ]; do
+    if [ -z "${1}" ] && [ -z "${jars}" ]; then
+      echo "Error: more parameters expected for runtest; please see usage for details"
+      cmd="help"
+      break
+    fi
+    if [ -z "${1}" ]; then
+      break
+    fi
+    jar=${1}
+    if [ "${1:0:1}" = "-" ]; then
+      # we are done with jars, starting with parameters now
+      break
+    fi
+    # if relative path, append the default path prefix
+    if [ "${1:0:1}" != "/" ]; then
+      jar=${run_base}/${1}
+    fi
+    # about to add the file to class path, check if it's valid
+    if [ ! -f ${jar} ]; then
+      echo "Error: ${jar} does not exist"
+      # force to print help message
+      cmd="help"
+      break
+    fi
+    jars=${jars}:${jar}
+    # done processing current arg, moving on
+    shift
+  done
+  # look for --nohup: if found, consume it and trap SIG_HUP, otherwise just
+  # append the arg to args
+  while [ -n "${1}" ]; do
+    if [ "${1}" = "--nohup" ]; then
+      trap "" HUP
+      shift
+    else
+      args="${args} ${1}"
+      shift
+    fi
+  done
+else
+  # if cmd is not 'runtest', just take the rest of the args
+  args=${@}
+fi
+
+args="${cmd} ${args}"
+if [ -n "${jars}" ]; then
+   args="${args} -e jars ${jars}"
+fi
+
+CLASSPATH=${CLASSPATH}:${jars}
+export CLASSPATH
+exec app_process ${base}/bin com.android.commands.uiautomator.Launcher ${args}
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
new file mode 100644
index 0000000..0c93b4c
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java b/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java
new file mode 100644
index 0000000..1d4fb93
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java b/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
new file mode 100644
index 0000000..f0c60d2
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/InstrumentationAutomationSupport.java b/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/InstrumentationAutomationSupport.java
new file mode 100644
index 0000000..a70586e
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java b/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java
new file mode 100644
index 0000000..ae763f2
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/cmds/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
new file mode 100644
index 0000000..b5f21c9
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk
new file mode 100644
index 0000000..7cc0884
--- /dev/null
+++ b/cmds/uiautomator/library/Android.mk
@@ -0,0 +1,132 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+uiautomator.core_src_files := $(call all-java-files-under, core-src) \
+	$(call all-java-files-under, testrunner-src)
+uiautomator.core_java_libraries := android.test.runner core-junit
+
+uiautomator_internal_api_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_api.txt
+uiautomator_internal_removed_api_file := \
+    $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_removed_api.txt
+
+###############################################
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(uiautomator.core_src_files)
+LOCAL_MODULE := uiautomator.core
+LOCAL_JAVA_LIBRARIES := android.test.runner
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+###############################################
+# Generate the stub source files
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(uiautomator.core_src_files)
+LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \
+	$(LOCAL_PATH)/testrunner-src
+LOCAL_DROIDDOC_HTML_DIR :=
+
+LOCAL_DROIDDOC_OPTIONS:= \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_uiautomator_intermediates/src \
+    -stubpackages com.android.uiautomator.core:com.android.uiautomator.testrunner \
+    -api $(uiautomator_internal_api_file) \
+    -removedApi $(uiautomator_internal_removed_api_file)
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
+LOCAL_UNINSTALLABLE_MODULE := true
+
+LOCAL_MODULE := uiautomator-stubs
+
+include $(BUILD_DROIDDOC)
+uiautomator_stubs_stamp := $(full_target)
+$(uiautomator_internal_api_file) : $(full_target)
+
+###############################################
+# Build the stub source files into a jar.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android_uiautomator
+LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+include $(BUILD_STATIC_JAVA_LIBRARY)
+# Make sure to run droiddoc first to generate the stub source files.
+$(full_classes_compiled_jar) : $(uiautomator_stubs_stamp)
+uiautomator_stubs_jar := $(full_classes_compiled_jar)
+
+###############################################
+# API check
+# Please refer to build/core/tasks/apicheck.mk.
+uiautomator_api_dir := frameworks/base/cmds/uiautomator/api
+last_released_sdk_version := $(lastword $(call numerically_sort, \
+    $(filter-out current, \
+        $(patsubst $(uiautomator_api_dir)/%.txt,%, $(wildcard $(uiautomator_api_dir)/*.txt)) \
+    )))
+
+checkapi_last_error_level_flags := \
+    -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18
+
+# Check that the API we're building hasn't broken the last-released SDK version.
+$(eval $(call check-api, \
+    uiautomator-checkapi-last, \
+    $(uiautomator_api_dir)/$(last_released_sdk_version).txt, \
+    $(uiautomator_internal_api_file), \
+    $(uiautomator_api_dir)/removed.txt, \
+    $(uiautomator_internal_removed_api_file), \
+    $(checkapi_last_error_level_flags), \
+    cat $(LOCAL_PATH)/apicheck_msg_last.txt, \
+    $(uiautomator_stubs_jar), \
+    $(uiautomator_stubs_stamp)))
+
+checkapi_current_error_level_flags := \
+    -error 2 -error 3 -error 4 -error 5 -error 6 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+    -error 25
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+    uiautomator-checkapi-current, \
+    $(uiautomator_api_dir)/current.txt, \
+    $(uiautomator_internal_api_file), \
+    $(uiautomator_api_dir)/removed.txt, \
+    $(uiautomator_internal_removed_api_file), \
+    $(checkapi_current_error_level_flags), \
+    cat $(LOCAL_PATH)/apicheck_msg_current.txt, \
+    $(uiautomator_stubs_jar), \
+    $(uiautomator_stubs_stamp)))
+
+.PHONY: update-uiautomator-api
+update-uiautomator-api: PRIVATE_API_DIR := $(uiautomator_api_dir)
+update-uiautomator-api: PRIVATE_REMOVED_API_FILE := $(uiautomator_internal_removed_api_file)
+update-uiautomator-api: $(uiautomator_internal_api_file) | $(ACP)
+	@echo Copying uiautomator current.txt
+	$(hide) $(ACP) $< $(PRIVATE_API_DIR)/current.txt
+	@echo Copying uiautomator removed.txt
+	$(hide) $(ACP) $(PRIVATE_REMOVED_API_FILE) $(PRIVATE_API_DIR)/removed.txt
+###############################################
+# clean up temp vars
+uiautomator.core_src_files :=
+uiautomator.core_java_libraries :=
+uiautomator_stubs_stamp :=
+uiautomator_internal_api_file :=
+uiautomator_stubs_jar :=
+uiautomator_api_dir :=
+checkapi_last_error_level_flags :=
+checkapi_current_error_level_flags :=
diff --git a/cmds/uiautomator/library/apicheck_msg_current.txt b/cmds/uiautomator/library/apicheck_msg_current.txt
new file mode 100644
index 0000000..989248d
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/apicheck_msg_last.txt b/cmds/uiautomator/library/apicheck_msg_last.txt
new file mode 100644
index 0000000..2993157
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
new file mode 100644
index 0000000..63c51e8
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
new file mode 100644
index 0000000..54835e3
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/Configurator.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/Configurator.java
new file mode 100644
index 0000000..249f404
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
new file mode 100644
index 0000000..73e46f1
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/QueryController.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/QueryController.java
new file mode 100644
index 0000000..6931528
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/Tracer.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/Tracer.java
new file mode 100644
index 0000000..d574fc0
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
new file mode 100644
index 0000000..bc5bc8e
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiCollection.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiCollection.java
new file mode 100644
index 0000000..e15beb2
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
new file mode 100644
index 0000000..a930eb4
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
new file mode 100644
index 0000000..4bb99cd
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java
new file mode 100644
index 0000000..fc0891b
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java
new file mode 100644
index 0000000..a8d20c3
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java
new file mode 100644
index 0000000..482a74d
--- /dev/null
+++ b/cmds/uiautomator/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.
+     *
+     * @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/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiWatcher.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiWatcher.java
new file mode 100644
index 0000000..5403e30
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
new file mode 100644
index 0000000..c551482
--- /dev/null
+++ b/cmds/uiautomator/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.isInteractive();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error getting screen status", e);
+            throw new RuntimeException(e);
+        }
+        return ret;
+    }
+}
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
new file mode 100644
index 0000000..1fa9bac
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
new file mode 100644
index 0000000..f0c60d2
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
new file mode 100644
index 0000000..cda49f6
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
new file mode 100644
index 0000000..e7d961b
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
new file mode 100644
index 0000000..1de5a4d
--- /dev/null
+++ b/cmds/uiautomator/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/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
new file mode 100644
index 0000000..ef167f9
--- /dev/null
+++ b/cmds/uiautomator/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/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 4853b81..a0a0716 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -58,6 +58,11 @@
     private static final int GLES_VERSION = 2;
     private static final int PBUFFER_PIXEL_BYTES = 4;
 
+    private static final int FLIP_TYPE_NONE = 0;
+    private static final int FLIP_TYPE_HORIZONTAL = 1;
+    private static final int FLIP_TYPE_VERTICAL = 2;
+    private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
+
     private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
     private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
     private EGLConfig mConfigs;
@@ -82,8 +87,8 @@
     private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
     private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
 
-    // Sampling is mirrored across the vertical axis to undo horizontal flip from the front camera
-    private static final float[] sFrontCameraTriangleVertices = {
+    // Sampling is mirrored across the horizontal axis
+    private static final float[] sHorizontalFlipTriangleVertices = {
             // X, Y, Z, U, V
             -1.0f, -1.0f, 0, 1.f, 0.f,
             1.0f, -1.0f, 0, 0.f, 0.f,
@@ -91,8 +96,26 @@
             1.0f,  1.0f, 0, 0.f, 1.f,
     };
 
+    // Sampling is mirrored across the vertical axis
+    private static final float[] sVerticalFlipTriangleVertices = {
+            // X, Y, Z, U, V
+            -1.0f, -1.0f, 0, 0.f, 1.f,
+            1.0f, -1.0f, 0, 1.f, 1.f,
+            -1.0f,  1.0f, 0, 0.f, 0.f,
+            1.0f,  1.0f, 0, 1.f, 0.f,
+    };
+
+    // Sampling is mirrored across the both axes
+    private static final float[] sBothFlipTriangleVertices = {
+            // X, Y, Z, U, V
+            -1.0f, -1.0f, 0, 1.f, 1.f,
+            1.0f, -1.0f, 0, 0.f, 1.f,
+            -1.0f,  1.0f, 0, 1.f, 0.f,
+            1.0f,  1.0f, 0, 0.f, 0.f,
+    };
+
     // Sampling is 1:1 for a straight copy for the back camera
-    private static final float[] sBackCameraTriangleVertices = {
+    private static final float[] sRegularTriangleVertices = {
             // X, Y, Z, U, V
             -1.0f, -1.0f, 0, 0.f, 0.f,
             1.0f, -1.0f, 0, 1.f, 0.f,
@@ -100,7 +123,11 @@
             1.0f,  1.0f, 0, 1.f, 1.f,
     };
 
-    private FloatBuffer mTriangleVertices;
+    private FloatBuffer mRegularTriangleVertices;
+    private FloatBuffer mHorizontalFlipTriangleVertices;
+    private FloatBuffer mVerticalFlipTriangleVertices;
+    private FloatBuffer mBothFlipTriangleVertices;
+    private final int mFacing;
 
     /**
      * As used in this file, this vertex shader maps a unit square to the view, and
@@ -148,15 +175,27 @@
     private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
 
     public SurfaceTextureRenderer(int facing) {
-        if (facing == CameraCharacteristics.LENS_FACING_BACK) {
-            mTriangleVertices = ByteBuffer.allocateDirect(sBackCameraTriangleVertices.length *
-                    FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
-            mTriangleVertices.put(sBackCameraTriangleVertices).position(0);
-        } else {
-            mTriangleVertices = ByteBuffer.allocateDirect(sFrontCameraTriangleVertices.length *
-                    FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
-            mTriangleVertices.put(sFrontCameraTriangleVertices).position(0);
-        }
+        mFacing = facing;
+
+        mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
+                FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
+
+        mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
+                sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+                order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
+
+        mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
+                sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+                order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
+
+        mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
+                sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+                order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
+
         Matrix.setIdentityM(mSTMatrix, 0);
     }
 
@@ -209,7 +248,7 @@
         return program;
     }
 
-    private void drawFrame(SurfaceTexture st, int width, int height) {
+    private void drawFrame(SurfaceTexture st, int width, int height, int flipType) {
         checkGlError("onDrawFrame start");
         st.getTransformMatrix(mSTMatrix);
 
@@ -266,16 +305,32 @@
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
 
-        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+        FloatBuffer triangleVertices;
+        switch(flipType) {
+            case FLIP_TYPE_HORIZONTAL:
+                triangleVertices = mHorizontalFlipTriangleVertices;
+                break;
+            case FLIP_TYPE_VERTICAL:
+                triangleVertices = mVerticalFlipTriangleVertices;
+                break;
+            case FLIP_TYPE_BOTH:
+                triangleVertices = mBothFlipTriangleVertices;
+                break;
+            default:
+                triangleVertices = mRegularTriangleVertices;
+                break;
+        }
+
+        triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
         GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
-                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
         checkGlError("glVertexAttribPointer maPosition");
         GLES20.glEnableVertexAttribArray(maPositionHandle);
         checkGlError("glEnableVertexAttribArray maPositionHandle");
 
-        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+        triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
         GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
-                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+                /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
         checkGlError("glVertexAttribPointer maTextureHandle");
         GLES20.glEnableVertexAttribArray(maTextureHandle);
         checkGlError("glEnableVertexAttribArray maTextureHandle");
@@ -666,7 +721,9 @@
                     makeCurrent(holder.eglSurface);
 
                     LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
-                    drawFrame(mSurfaceTexture, holder.width, holder.height);
+                    drawFrame(mSurfaceTexture, holder.width, holder.height,
+                            (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
+                                    FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
                     swapBuffers(holder.eglSurface);
                 } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                     Log.w(TAG, "Surface abandoned, dropping frame. ", e);
@@ -676,7 +733,10 @@
         for (EGLSurfaceHolder holder : mConversionSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
                 makeCurrent(holder.eglSurface);
-                drawFrame(mSurfaceTexture, holder.width, holder.height);
+                // glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
+                drawFrame(mSurfaceTexture, holder.width, holder.height,
+                        (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
+                                FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
                 mPBufferPixels.clear();
                 GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
                         GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 6ddd8b3..9b80e74 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -274,4 +274,12 @@
     protected void log(String s) {
         Log.d(LOG_TAG, s);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - ScoreFilter=").
+                append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=").
+                append(mNetworkRequests.size()).append("}");
+        return sb.toString();
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 25a70eb..22c5185 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -825,6 +825,12 @@
             return false;
         }
 
+        // Clip the bounds by our bounds.
+        bounds.left = Math.max(bounds.left, 0);
+        bounds.top = Math.max(bounds.top, 0);
+        bounds.right = Math.min(bounds.right, mRight);
+        bounds.bottom = Math.min(bounds.bottom, mBottom);
+
         Iterator<View> iterator = obtainOrderedChildIterator();
         while (iterator.hasNext()) {
             View sibling = iterator.next();
@@ -5113,7 +5119,8 @@
         final int height = mBottom - mTop;
 
         boolean rectIsVisible = true;
-        if (mParent instanceof ViewGroup && ((ViewGroup)mParent).getClipChildren()) {
+        if (mParent == null ||
+                (mParent instanceof ViewGroup && ((ViewGroup) mParent).getClipChildren())) {
             // Clip to bounds.
             rectIsVisible = rect.intersect(0, 0, width, height);
         }
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 75c6184..7b64cf5 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -1456,6 +1456,32 @@
 
             final boolean selected = isVirtualViewSelected(type, value);
             node.setSelected(selected);
+
+            final int nextId = getVirtualViewIdAfter(type, value);
+            if (nextId != INVALID_ID) {
+                node.setTraversalBefore(RadialTimePickerView.this, nextId);
+            }
+        }
+
+        private int getVirtualViewIdAfter(int type, int value) {
+            if (type == TYPE_HOUR) {
+                final int nextValue = value + 1;
+                final int max = mIs24HourMode ? 23 : 12;
+                if (nextValue <= max) {
+                    return makeId(type, nextValue);
+                }
+            } else if (type == TYPE_MINUTE) {
+                final int current = getCurrentMinute();
+                final int snapValue = value - (value % MINUTE_INCREMENT);
+                final int nextValue = snapValue + MINUTE_INCREMENT;
+                if (value < current && nextValue > current) {
+                    // The current value is between two snap values.
+                    return makeId(type, current);
+                } else if (nextValue < 60) {
+                    return makeId(type, nextValue);
+                }
+            }
+            return INVALID_ID;
         }
 
         @Override
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index d61b6fc..8d475a7 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -33,9 +33,11 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.AccessibilityDelegate;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.R;
 
@@ -136,9 +138,13 @@
         // Set up hour/minute labels.
         mHourView = (TextView) mHeaderView.findViewById(R.id.hours);
         mHourView.setOnClickListener(mClickListener);
+        mHourView.setAccessibilityDelegate(
+                new ClickActionDelegate(context, R.string.select_hours));
         mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator);
         mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes);
         mMinuteView.setOnClickListener(mClickListener);
+        mMinuteView.setAccessibilityDelegate(
+                new ClickActionDelegate(context, R.string.select_minutes));
 
         final int headerTimeTextAppearance = a.getResourceId(
                 R.styleable.TimePicker_headerTimeTextAppearance, 0);
@@ -206,6 +212,22 @@
         initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
     }
 
+    private static class ClickActionDelegate extends AccessibilityDelegate {
+        private final AccessibilityAction mClickAction;
+
+        public ClickActionDelegate(Context context, int resId) {
+            mClickAction = new AccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_CLICK, context.getString(resId));
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+
+            info.addAction(mClickAction);
+        }
+    }
+
     private int computeStableWidth(TextView v, int maxNumber) {
         int maxWidth = 0;
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 634d03d..661acbe 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -248,12 +248,14 @@
         }
         mAlwaysUseOption = alwaysUseOption;
 
-        int count = mAdapter.mList.size();
         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
             // Gulp!
             finish();
             return;
-        } else if (count > 1) {
+        }
+
+        int count = mAdapter.mList.size();
+        if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) {
             setContentView(layoutId);
             mListView = (ListView) findViewById(R.id.resolver_list);
             mListView.setAdapter(mAdapter);
@@ -797,11 +799,9 @@
         }
 
         public void handlePackagesChanged() {
-            final int oldItemCount = getCount();
             rebuildList();
             notifyDataSetChanged();
-            final int newItemCount = getCount();
-            if (newItemCount == 0) {
+            if (getCount() == 0) {
                 // We no longer have any items...  just finish the activity.
                 finish();
             }
@@ -956,6 +956,13 @@
                 // Process last group
                 processGroup(currentResolveList, start, (N-1), r0, r0Label);
             }
+
+            // Layout doesn't handle both profile button and last chosen
+            // so disable last chosen if profile button is present.
+            if (mOtherProfile != null && mLastChosenPosition >= 0) {
+                mLastChosenPosition = -1;
+                mFilterLastUsed = false;
+            }
         }
 
         private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
@@ -963,14 +970,9 @@
             // Process labels from start to i
             int num = end - start+1;
             if (num == 1) {
-                if (mLastChosen != null
-                        && mLastChosen.activityInfo.packageName.equals(
-                                ro.activityInfo.packageName)
-                        && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
-                    mLastChosenPosition = mList.size();
-                }
                 // No duplicate labels. Use label for entry at start
                 addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null));
+                updateLastChosenPosition(ro);
             } else {
                 mShowExtended = true;
                 boolean usePkg = false;
@@ -998,12 +1000,6 @@
                 }
                 for (int k = start; k <= end; k++) {
                     ResolveInfo add = rList.get(k);
-                    if (mLastChosen != null
-                            && mLastChosen.activityInfo.packageName.equals(
-                                    add.activityInfo.packageName)
-                            && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
-                        mLastChosenPosition = mList.size();
-                    }
                     if (usePkg) {
                         // Use application name for all entries from start to end-1
                         addResolveInfo(new DisplayResolveInfo(add, roLabel,
@@ -1013,10 +1009,19 @@
                         addResolveInfo(new DisplayResolveInfo(add, roLabel,
                                 add.activityInfo.applicationInfo.loadLabel(mPm), null));
                     }
+                    updateLastChosenPosition(add);
                 }
             }
         }
 
+        private void updateLastChosenPosition(ResolveInfo info) {
+            if (mLastChosen != null
+                    && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
+                    && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
+                mLastChosenPosition = mList.size() - 1;
+            }
+        }
+
         private void addResolveInfo(DisplayResolveInfo dri) {
             if (dri.ri.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) {
                 // So far we only support a single other profile at a time.
@@ -1217,4 +1222,3 @@
         }
     }
 }
-
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index b44c829..b27add8 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -49,36 +49,35 @@
 #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
 
 /**
- * Convert from RGB 888 to Y'CbCr using the conversion specified in ITU-R BT.601 for
- * digital RGB with K_b = 0.114, and K_r = 0.299.
+ * Convert from RGB 888 to Y'CbCr using the conversion specified in JFIF v1.02
  */
 static void rgbToYuv420(uint8_t* rgbBuf, size_t width, size_t height, uint8_t* yPlane,
         uint8_t* uPlane, uint8_t* vPlane, size_t chromaStep, size_t yStride, size_t chromaStride) {
     uint8_t R, G, B;
     size_t index = 0;
-
-    size_t cStrideDiff = chromaStride - width;
-
     for (size_t j = 0; j < height; j++) {
+        uint8_t* u = uPlane;
+        uint8_t* v = vPlane;
+        uint8_t* y = yPlane;
+        bool jEven = (j & 1) == 0;
         for (size_t i = 0; i < width; i++) {
             R = rgbBuf[index++];
             G = rgbBuf[index++];
             B = rgbBuf[index++];
-            *(yPlane + i) = ((66 * R + 129 * G +  25 * B + 128) >> 8) +  16;
-
-            if (j % 2 == 0 && i % 2 == 0){
-                *uPlane = (( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128;
-                *vPlane = (( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128;
-                uPlane += chromaStep;
-                vPlane += chromaStep;
+            *y++ = (77 * R + 150 * G +  29 * B) >> 8;
+            if (jEven && (i & 1) == 0) {
+                *v = (( -43 * R - 85 * G + 128 * B) >> 8) + 128;
+                *u = (( 128 * R - 107 * G - 21 * B) >> 8) + 128;
+                u += chromaStep;
+                v += chromaStep;
             }
             // Skip alpha
             index++;
         }
         yPlane += yStride;
-        if (j % 2 == 0) {
-            uPlane += cStrideDiff;
-            vPlane += cStrideDiff;
+        if (jEven) {
+            uPlane += chromaStride;
+            vPlane += chromaStride;
         }
     }
 }
@@ -87,8 +86,10 @@
     size_t cStep = ycbcr->chroma_step;
     size_t cStride = ycbcr->cstride;
     size_t yStride = ycbcr->ystride;
+    ALOGV("%s: yStride is: %zu, cStride is: %zu, cStep is: %zu", __FUNCTION__, yStride, cStride,
+            cStep);
     rgbToYuv420(rgbBuf, width, height, reinterpret_cast<uint8_t*>(ycbcr->y),
-            reinterpret_cast<uint8_t*>(ycbcr->cb), reinterpret_cast<uint8_t*>(ycbcr->cr),
+            reinterpret_cast<uint8_t*>(ycbcr->cr), reinterpret_cast<uint8_t*>(ycbcr->cb),
             cStep, yStride, cStride);
 }
 
@@ -231,6 +232,7 @@
 
     size_t totalSizeBytes = tmpSize;
 
+    ALOGV("%s: Pixel format chosen: %x", __FUNCTION__, pixelFmt);
     switch(pixelFmt) {
         case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
             if (bufferLength < totalSizeBytes) {
@@ -276,6 +278,7 @@
             }
 
             uint32_t stride = buf->getStride();
+            ALOGV("%s: stride is: %" PRIu32, __FUNCTION__, stride);
             LOG_ALWAYS_FATAL_IF(stride % 16, "Stride is not 16 pixel aligned %d", stride);
 
             uint32_t cStride = ALIGN(stride / 2, 16);
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 5a32718..d9e64c7 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -528,8 +528,8 @@
                         c++;
                     }
                     pss += atoi(c);
-                } else if (strncmp(line, "Private_Clean:", 14)
-                        || strncmp(line, "Private_Dirty:", 14)) {
+                } else if (strncmp(line, "Private_Clean:", 14) == 0
+                        || strncmp(line, "Private_Dirty:", 14) == 0) {
                     char* c = line + 14;
                     while (*c != 0 && (*c < '0' || *c > '9')) {
                         c++;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8bc6993..72ff8eb 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1978,7 +1978,9 @@
     <!-- An array of CDMA roaming indicators which means international roaming -->
     <integer-array translatable="false" name="config_cdma_international_roaming_indicators" />
 
-
     <!-- set the system language as value of EF LI/EF PL -->
     <bool name="config_use_sim_language_file">true</bool>
+
+    <!-- Use ERI text for network name on CDMA LTE -->
+    <bool name="config_LTE_eri_for_network_name">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 758ef5f..3dd783f 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2149,4 +2149,5 @@
   <java-symbol type="string" name="kg_text_message_separator" />
 
   <java-symbol type="bool" name="config_use_sim_language_file" />
+  <java-symbol type="bool" name="config_LTE_eri_for_network_name" />
 </resources>
diff --git a/docs/html/about/versions/android-5.0-changes.jd b/docs/html/about/versions/android-5.0-changes.jd
index f12e83c..3de5c3c 100644
--- a/docs/html/about/versions/android-5.0-changes.jd
+++ b/docs/html/about/versions/android-5.0-changes.jd
@@ -1,9 +1,10 @@
-page.title=Android 5.0 Changes
+page.title=Android 5.0 Behavior Changes
 excludeFromSuggestions=true
 sdk.platform.version=5.0
 sdk.platform.apiLevel=21
 @jd:body
 
+<!-- video box -->
 
 <div id="qv-wrapper">
 <div id="qv">
@@ -20,8 +21,16 @@
   <li><a href="#Power"><a href="#BehaviorWebView">WebView</a></a></li>
   <li><a href="#custom_permissions">Custom Permissions</a></li>
   <li><a href="#ssl">TLS/SSL Configuration</a></li>
+  <li><a href="#managed_profiles">Support for Managed Profiles</a></li>
 </ol>
 
+<a class="notice-developers-video" href="https://www.youtube.com/watch?v=Uiq2kZ2JHVY">
+<div>
+    <h3>Video</h3>
+    <p>Notifications</p>
+</div>
+</a>
+
 <h2>API Differences</h2>
 <ol>
 <li><a href="{@docRoot}sdk/api_diff/21/changes.html">API level 20 to 21 &raquo;</a> </li>
@@ -40,9 +49,8 @@
 </div>
 
 <p>API Level: {@sdkPlatformApiLevel}</p>
-<p>Along with new features and capabilities, Android 5.0 includes a variety of changes
-API changes,
-behavior changes, system enhancements, and bug fixes. This document highlights
+<p>Along with new features and capabilities, Android 5.0 includes a variety of
+system changes and API behavior changes. This document highlights
 some of the key changes that you should be understand and account for in your apps.</p>
 
 <p>If you have previously published an app for Android, be aware that your app
@@ -296,8 +304,8 @@
 
 <p>
   Android 5.0 includes a behavior change to ensure
-  that only one app can define a given custom permission, unless signed with the 
-  same key as other apps defining the permission. 
+  that only one app can define a given custom permission, unless signed with the
+  same key as other apps defining the permission.
 </p>
 
 <h3>
@@ -316,7 +324,7 @@
 <p>
   In Android 4.4 and earlier, users were able to install multiple such
   apps on a given device, although the system assigned the protection level
-  specified by the first-installed app. 
+  specified by the first-installed app.
 </p>
 
 <p>
@@ -522,4 +530,64 @@
   communicate with the server. The factory should be designed to create
   SSLSocket instances with only those protocols enabled which are correctly
   supported by the server.
-</p>
\ No newline at end of file
+</p>
+
+<h2 id="managed_profiles">Support for Managed Profiles</h2>
+
+<p>
+  Device administrators can add a <em>managed profile</em> to a device. This
+  profile is owned by the administrator, giving the administrator control
+  over the managed profile while leaving the user's personal profile, and its
+  storage space, under the user's control.
+  This change can affect the behavior of your existing app in
+  the following ways.</p>
+
+<h3 id="mg_profile_intents">Handling intents</h3>
+
+<p>Device administrators can restrict access to system applications from the
+managed profile. In this case, if an app fires an intent from the managed
+profile that would ordinarily be handled by that application, and there is no
+suitable handler for the intent on the managed profile,
+the intent causes an exception. For example, the
+device administrator can restrict apps on the managed profile from accessing
+the system's camera application. If your app is running on the managed profile
+and calls {@link
+android.app.Activity#startActivityForResult startActivityForResult()} for {@link
+android.provider.MediaStore#ACTION_IMAGE_CAPTURE
+MediaStore.ACTION_IMAGE_CAPTURE}, and there is no app on the managed profile
+that can handle the intent, this results in an {@link
+android.content.ActivityNotFoundException}.</p>
+
+<p>You can prevent this by checking
+that there is at least one handler for any intent
+before firing it. To check for a valid handler, call {@link
+android.content.Intent#resolveActivity Intent.resolveActivity()}. You can see
+an example of this being done in <a
+href="{@docRoot}training/camera/photobasics.html#TaskCaptureIntent">Take Photos
+Simply: Take a Photo with the Camera App</a>.</p>
+
+<h3 id="mp_profile_file_sharing">Sharing files across profiles</h3>
+
+<p>Each profile has its own file storage. Since a file URI refers to a specific
+location in the file storage, this means that a file URI that is valid on one
+profile is not valid on the other one. This is not ordinarily a problem for an
+app, which usually just accesses the files it creates. However, if an app
+attaches a file to an intent, it is not safe to attach a file URI, since in some
+circumstances, the intent might be handled on the other profile.
+For example, a device administrator might specify that image capture events
+should be handled by the camera app on the personal profile. If the intent is
+fired by an app on the managed profile, the camera needs to be able to write the
+image to a location where the managed profile's apps can read it.</p>
+
+<p>To be safe, when
+you need to attach a file to an intent that might cross from one profile to the
+other, you should create and use a <em>content URI</em> for the file. For more
+information about sharing files with content URIs, see <a
+href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.
+For example, the device administrator might whitelist {@link
+android.provider.MediaStore#ACTION_IMAGE_CAPTURE ACTION_IMAGE_CAPTURE} to be
+handled by the camera in the personal profile. The firing intent's {@link
+android.provider.MediaStore#EXTRA_OUTPUT EXTRA_OUTPUT} should contain a content
+URI specifying where the photo should be stored. The camera app can write the
+image to the location specified by that URI, and the app that fired the intent
+would be able to read that file, even if the app is on the other profile. </p>
diff --git a/docs/html/design/wear/index.jd b/docs/html/design/wear/index.jd
index 104e154..c75723f 100644
--- a/docs/html/design/wear/index.jd
+++ b/docs/html/design/wear/index.jd
@@ -2,7 +2,6 @@
 @jd:body
 
 
-
 <p>Designing apps for wearable devices powered by Android Wear
 is substantially different than designing for phones or
 tablets: different strengths and weaknesses, different use cases, different ergonomics.
diff --git a/docs/html/design/wear/watchfaces.jd b/docs/html/design/wear/watchfaces.jd
index ef700ee..1a4b1f9 100644
--- a/docs/html/design/wear/watchfaces.jd
+++ b/docs/html/design/wear/watchfaces.jd
@@ -4,10 +4,10 @@
 
 <!-- developer docs box -->
 <a class="notice-developers right" href="{@docRoot}training/wearables/watch-faces/index.html"
-   style="clear:left;margin-bottom:70px">
+   style="clear:left;margin-bottom:90px">
   <div>
     <h3>Developer Docs</h3>
-    <p>Creating Custom Watch Faces</p>
+    <p>Creating Watch Faces</p>
   </div>
 </a>
 
@@ -158,8 +158,8 @@
   burn-in effect. When these screens are in ambient mode, the system shifts the contents of
   the screen periodically by a few pixels to avoid pixel burn-in. Do not use large blocks of
   pixels in your ambient mode designs and keep 95% of the pixels black. Replace solid shapes in
-  your regular ambient mode designs with outlined shapes in burn-protected ambient mode. Replace
-  also filled images with pixel patterns. For analog watch face designs, hollow out the center
+  your regular ambient mode designs with outlined shapes in burn-protected ambient mode. Also
+  replace filled images with pixel patterns. For analog watch face designs, hollow out the center
   where the hands meet to avoid pixel burn-in in this mode.</p>
 </div>
 <div class="layout-content-col span-4">
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index 986862b..e56f3f5 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -15,8 +15,8 @@
 studio.mac_bundle_checksum=0d9e0e230ece9f2e696b1b076c36ee1e73edcf3c
 
 studio.win_bundle_exe_download=android-studio-bundle-135.1629389.exe
-studio.win_bundle_exe_bytes=852499624
-studio.win_bundle_exe_checksum=0c8a3b45385a698b43a47757fdd6a83ca837abd2
+studio.win_bundle_exe_bytes=868337656
+studio.win_bundle_exe_checksum=1931dbaeadb52f5e0a8ba6e2ae60d9df20b2076b
 
 studio.win_notools_exe_download=android-studio-ide-135.1629389.exe
 studio.win_notools_exe_bytes=262099808
diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd
index 75b3cef..a73be5e 100644
--- a/docs/html/tools/revisions/platforms.jd
+++ b/docs/html/tools/revisions/platforms.jd
@@ -59,6 +59,22 @@
 <div class="toggle-content opened">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png"
+class="toggle-content-img" alt="" />Revision 2</a> <em>(December 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+
+    <p>Updated layouts in the Support Library and fixed various issues.</p>
+    <p>Dependencies:</p>
+    <ul>
+      <li>Android SDK Platform-tools r21 or higher is required.</li>
+      <li>Android SDK Tools 23.0.5 or higher is required.</li>
+    </ul>
+  </div>
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png"
 class="toggle-content-img" alt="" />Revision 1</a> <em>(October 2014)</em>
   </p>
 
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index 80edb4f..ed48887 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -25,6 +25,33 @@
 <div class="toggle-content opened">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+      alt=""/>SDK Tools, Revision 24.0.1</a> <em>(December 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+
+    <dl>
+    <dt>Dependencies:</dt>
+
+    <dd>
+      <ul>
+        <li>Android SDK Platform-tools revision 19 or later.</li>
+      </ul>
+    </dd>
+
+    <dt>General Notes:</dt>
+    <dd>
+      <ul>
+        <li>Fixed Java detection issue on 32-bit Windows systems.</li>
+      </ul>
+    </dd>
+  </div>
+</div>
+
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
       alt=""/>SDK Tools, Revision 24.0.0</a> <em>(December 2014)</em>
   </p>
 
@@ -49,6 +76,7 @@
 </div>
 
 
+
 <div class="toggle-content closed">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
diff --git a/docs/html/tools/studio/index.jd b/docs/html/tools/studio/index.jd
index 258fedb..7480f57 100644
--- a/docs/html/tools/studio/index.jd
+++ b/docs/html/tools/studio/index.jd
@@ -235,8 +235,8 @@
 
 <p>The AVD Manager comes with emulators for Nexus 6 and Nexus 9 devices and also supports
 creating custom Android device skins based on specific emulator properties and assigning those
-skins to hardware profiles. Android Studio installs the the Intel x86 Emulator Accelerator (HAXM)
-and creates a default emulator for quick app prototyping.</p>
+skins to hardware profiles. Android Studio installs the Intel&#174; x86 Hardware Accelerated Execution
+Manager (HAXM) emulator accelerator and creates a default emulator for quick app prototyping.</p>
 
 <p>For more information, see <a href="{@docRoot}tools/devices/managing-avds.html">Managing AVDs</a>.</p>
 
@@ -334,7 +334,7 @@
 <p>An updated installation and setup wizards walk you through a step-by-step installation
 and setup process as the wizard checks for system requirements, such as the Java Development
 Kit (JDK) and available RAM, and then prompts for optional installation options, such as the
-Intel &#174; HAXM accelerator.</p>
+Intel&#174; HAXM emulator accelerator.</p>
 
 <p>An updated setup wizard walks you through the setup processes as
 the wizard updates your system image and emulation requirements, such GPU, and then creates
@@ -386,7 +386,7 @@
 
 
 
-<h2 id="other">Other Highlights/h2>
+<h2 id="other">Other Highlights</h2>
 
 <h3> Translation Editor</h3>
 <p>Multi-language support is enhanced with the Translation Editor plugin so you can easily add
@@ -414,5 +414,5 @@
 <p>Clicking <strong>Import Samples</strong> from the <strong>File</strong> menu or Welcome page
 provides seamless access to Google code samples on GitHub.</p>
     <p><img src="{@docRoot}images/tools/studio-samples-githubaccess.png" /></p>
-    <p class="img-caption"><strong>Figure 12.</strong> Code Sample Access/p>
+    <p class="img-caption"><strong>Figure 12.</strong> Code Sample Access</p>
 
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index f883e25..f3b2693 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -838,8 +838,8 @@
       <li class="nav-section">
         <div class="nav-section-header">
           <a href="<?cs var:toroot ?>training/wearables/watch-faces/index.html"
-             description="How to create custom watch faces for wearables."
-            >Creating Custom Watch Faces</a>
+             description="How to create watch faces for wearables."
+            >Creating Watch Faces</a>
         </div>
         <ul>
           <li>
diff --git a/docs/html/training/wearables/watch-faces/designing.jd b/docs/html/training/wearables/watch-faces/designing.jd
index b7fcfd4..1033fed 100644
--- a/docs/html/training/wearables/watch-faces/designing.jd
+++ b/docs/html/training/wearables/watch-faces/designing.jd
@@ -16,6 +16,14 @@
 </div>
 </div>
 
+<!-- design guide box -->
+<a class="notice-designers wide" href="{@docRoot}design/wear/watchfaces.html">
+  <div>
+    <h3>Design Guide</h3>
+    <p>Watch Faces</p>
+  </div>
+</a>
+
 <p>Similar to the process of designing a traditional watch face, creating one for
 Android Wear is an exercise in visualizing time clearly. Android Wear devices
 provide advanced capabilities for watch faces that you can leverage in your designs, such as
diff --git a/docs/html/training/wearables/watch-faces/index.jd b/docs/html/training/wearables/watch-faces/index.jd
index c510fb2..c7affd1 100644
--- a/docs/html/training/wearables/watch-faces/index.jd
+++ b/docs/html/training/wearables/watch-faces/index.jd
@@ -1,4 +1,4 @@
-page.title=Creating Custom Watch Faces
+page.title=Creating Watch Faces
 
 @jd:body
 
@@ -13,6 +13,14 @@
 </div>
 </div>
 
+<!-- design guide box -->
+<a class="notice-designers wide" href="{@docRoot}design/wear/watchfaces.html">
+  <div>
+    <h3>Design Guide</h3>
+    <p>Watch Faces</p>
+  </div>
+</a>
+
 <p>Watch faces in Android Wear leverage a dynamic digital canvas to tell time using colors,
 animations, and relevant contextual information. The <a
 href="https://play.google.com/store/apps/details?id=com.google.android.wearable.app">Android
diff --git a/docs/html/training/wearables/watch-faces/service.jd b/docs/html/training/wearables/watch-faces/service.jd
index 0cb628c..87ebefa 100644
--- a/docs/html/training/wearables/watch-faces/service.jd
+++ b/docs/html/training/wearables/watch-faces/service.jd
@@ -75,7 +75,7 @@
 dependencies {
     ...
     wearApp project(':wear')
-    compile 'com.google.android.gms:play-services:6.1.+'
+    compile 'com.google.android.gms:play-services:6.5.+'
 }
 </pre>
 
@@ -90,7 +90,7 @@
 dependencies {
     ...
     compile 'com.google.android.support:wearable:1.1.+'
-    compile 'com.google.android.gms:play-services-wearable:6.1.+'
+    compile 'com.google.android.gms:play-services-wearable:6.5.+'
 }
 </pre>
 
@@ -183,7 +183,7 @@
         }
 
         &#64;Override
-        public void onDraw(Canvas canvas) {
+        public void onDraw(Canvas canvas, Rect bounds) {
             /* draw your watch face */
         }
 
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 0f0c844..e3b50ea 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -252,6 +252,11 @@
     }
 
     @Override
+    public boolean canApplyTheme() {
+        return mColorState.canApplyTheme() || super.canApplyTheme();
+    }
+
+    @Override
     public void applyTheme(Theme t) {
         super.applyTheme(t);
 
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 3fe408a..39ef10c 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -447,36 +447,10 @@
             mCurrDrawable = d;
             mCurIndex = idx;
             if (d != null) {
-                d.mutate();
                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
-                } else if (mHasAlpha) {
-                    d.setAlpha(mAlpha);
                 }
-                if (mDrawableContainerState.mHasColorFilter) {
-                    // Color filter always overrides tint.
-                    d.setColorFilter(mDrawableContainerState.mColorFilter);
-                } else {
-                    if (mDrawableContainerState.mHasTintList) {
-                        d.setTintList(mDrawableContainerState.mTintList);
-                    }
-                    if (mDrawableContainerState.mHasTintMode) {
-                        d.setTintMode(mDrawableContainerState.mTintMode);
-                    }
-                }
-                d.setVisible(isVisible(), true);
-                d.setDither(mDrawableContainerState.mDither);
-                d.setState(getState());
-                d.setLevel(getLevel());
-                d.setBounds(getBounds());
-                d.setLayoutDirection(getLayoutDirection());
-                d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
-
-                final Rect hotspotBounds = mHotspotBounds;
-                if (hotspotBounds != null) {
-                    d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
-                            hotspotBounds.right, hotspotBounds.bottom);
-                }
+                initializeDrawableForDisplay(d);
             }
         } else {
             mCurrDrawable = null;
@@ -503,6 +477,45 @@
         return true;
     }
 
+    /**
+     * Initializes a drawable for display in this container.
+     *
+     * @param d The drawable to initialize.
+     */
+    private void initializeDrawableForDisplay(Drawable d) {
+        d.mutate();
+
+        if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
+            d.setAlpha(mAlpha);
+        }
+
+        if (mDrawableContainerState.mHasColorFilter) {
+            // Color filter always overrides tint.
+            d.setColorFilter(mDrawableContainerState.mColorFilter);
+        } else {
+            if (mDrawableContainerState.mHasTintList) {
+                d.setTintList(mDrawableContainerState.mTintList);
+            }
+            if (mDrawableContainerState.mHasTintMode) {
+                d.setTintMode(mDrawableContainerState.mTintMode);
+            }
+        }
+
+        d.setVisible(isVisible(), true);
+        d.setDither(mDrawableContainerState.mDither);
+        d.setState(getState());
+        d.setLevel(getLevel());
+        d.setBounds(getBounds());
+        d.setLayoutDirection(getLayoutDirection());
+        d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+
+        final Rect hotspotBounds = mHotspotBounds;
+        if (hotspotBounds != null) {
+            d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
+                    hotspotBounds.right, hotspotBounds.bottom);
+        }
+    }
+
     void animate(boolean schedule) {
         mHasAlpha = true;
 
@@ -1136,9 +1149,14 @@
         // The locally cached drawables may have changed.
         if (mCurIndex >= 0) {
             mCurrDrawable = state.getChild(mCurIndex);
+            if (mCurrDrawable != null) {
+                initializeDrawableForDisplay(mCurrDrawable);
+            }
         }
-        if (mLastIndex >= 0) {
-            mLastDrawable = state.getChild(mLastIndex);
-        }
+
+        // Clear out the last drawable. We don't have enough information to
+        // propagate local state from the past.
+        mLastIndex = -1;
+        mLastDrawable = null;
     }
 }
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index a3a220c..ba1e86c 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -214,7 +214,7 @@
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
             // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(true);
+            cancelHardwareAnimations(false);
         }
         mCanUseHardware = canUseHardware;
 
@@ -493,7 +493,7 @@
     public void cancel() {
         mCanceled = true;
         cancelSoftwareAnimations();
-        cancelHardwareAnimations(true);
+        cancelHardwareAnimations(false);
         mCanceled = false;
     }
 
@@ -522,15 +522,30 @@
     /**
      * Cancels any running hardware animations.
      */
-    private void cancelHardwareAnimations(boolean cancelPending) {
+    private void cancelHardwareAnimations(boolean jumpToEnd) {
         final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
         final int N = runningAnimations.size();
         for (int i = 0; i < N; i++) {
-            runningAnimations.get(i).cancel();
+            if (jumpToEnd) {
+                runningAnimations.get(i).end();
+            } else {
+                runningAnimations.get(i).cancel();
+            }
         }
         runningAnimations.clear();
 
-        mHasPendingHardwareExit = false;
+        if (mHasPendingHardwareExit) {
+            // If we had a pending hardware exit, jump to the end state.
+            mHasPendingHardwareExit = false;
+
+            if (jumpToEnd) {
+                mOpacity = 0;
+                mTweenX = 1;
+                mTweenY = 1;
+                mTweenRadius = 1;
+            }
+        }
+
         mHardwareAnimating = false;
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 665d736..cc42aac 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -148,7 +148,7 @@
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
             // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(true);
+            cancelHardwareAnimations(false);
         }
         mCanUseHardware = canUseHardware;
 
@@ -399,7 +399,7 @@
      */
     public void cancel() {
         cancelSoftwareAnimations();
-        cancelHardwareAnimations(true);
+        cancelHardwareAnimations(false);
     }
 
     private void cancelSoftwareAnimations() {
@@ -412,15 +412,27 @@
     /**
      * Cancels any running hardware animations.
      */
-    private void cancelHardwareAnimations(boolean cancelPending) {
+    private void cancelHardwareAnimations(boolean jumpToEnd) {
         final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
         final int N = runningAnimations.size();
         for (int i = 0; i < N; i++) {
-            runningAnimations.get(i).cancel();
+            if (jumpToEnd) {
+                runningAnimations.get(i).end();
+            } else {
+                runningAnimations.get(i).cancel();
+            }
         }
         runningAnimations.clear();
 
-        mHasPendingHardwareExit = false;
+        if (mHasPendingHardwareExit) {
+            // If we had a pending hardware exit, jump to the end state.
+            mHasPendingHardwareExit = false;
+
+            if (jumpToEnd) {
+                mOuterOpacity = 0;
+            }
+        }
+
         mHardwareAnimating = false;
     }
 
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index c6de535..52ca92d 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -64,6 +64,12 @@
 
 void AssetAtlas::updateTextureId() {
     mTexture->id = mImage ? mImage->getTexture() : 0;
+    if (mTexture->id) {
+        // Texture ID changed, force-set to defaults to sync the wrapper & GL
+        // state objects
+        mTexture->setWrap(GL_CLAMP_TO_EDGE, false, true);
+        mTexture->setFilter(GL_NEAREST, false, true);
+    }
     for (size_t i = 0; i < mEntries.size(); i++) {
         AssetAtlas::Entry* entry = mEntries.valueAt(i);
         entry->texture->id = mTexture->id;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 810e487..787ee62 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -429,9 +429,9 @@
                 clipFlags = 0; // all clipping done by saveLayer
             }
 
-            ATRACE_FORMAT("%s alpha caused %ssaveLayer %ux%u",
+            ATRACE_FORMAT("%s alpha caused %ssaveLayer %dx%d",
                     getName(), clipFlags ? "" : "unclipped ",
-                    layerBounds.getWidth(), layerBounds.getHeight());
+                    (int)layerBounds.getWidth(), (int)layerBounds.getHeight());
 
             SaveLayerOp* op = new (handler.allocator()) SaveLayerOp(
                     layerBounds.left, layerBounds.top, layerBounds.right, layerBounds.bottom,
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index ea9e703..f0150d4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1159,8 +1159,13 @@
         synchronized (mHdmiManager) {
             if (!mHdmiSystemAudioSupported) return;
             synchronized (mHdmiTvClient) {
-                mHdmiTvClient.setSystemAudioVolume(
-                        (oldVolume + 5) / 10, (newVolume + 5) / 10, maxVolume);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mHdmiTvClient.setSystemAudioVolume(
+                            (oldVolume + 5) / 10, (newVolume + 5) / 10, maxVolume);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
         }
     }
@@ -1538,15 +1543,14 @@
     private void setSystemAudioMute(boolean state) {
         if (mHdmiManager == null || mHdmiTvClient == null) return;
         synchronized (mHdmiManager) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioMute(state);
-                    }
+            if (!mHdmiSystemAudioSupported) return;
+            synchronized (mHdmiTvClient) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mHdmiTvClient.setSystemAudioMute(state);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(token);
             }
         }
     }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 4513643..6984575 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -126,6 +126,7 @@
                      new Rational(Integer.MAX_VALUE, 1));
     private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
     private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+    private static final Range<Integer> BITRATE_RANGE = Range.create(0, 500000000);
 
     // found stuff that is not supported by framework (=> this should not happen)
     private static final int ERROR_UNRECOGNIZED   = (1 << 0);
@@ -711,7 +712,7 @@
             }
             if (info.containsKey("bitrate-range")) {
                 bitRates = bitRates.intersect(
-                        Utils.parseIntRange(info.getString("bitrate"), bitRates));
+                        Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
             }
             applyLimits(maxInputChannels, bitRates);
         }
@@ -1061,7 +1062,7 @@
         }
 
         private void initWithPlatformLimits() {
-            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+            mBitrateRange = BITRATE_RANGE;
 
             mWidthRange  = SIZE_RANGE;
             mHeightRange = SIZE_RANGE;
@@ -1090,7 +1091,7 @@
             Size blockSize = new Size(mBlockWidth, mBlockHeight);
             Size alignment = new Size(mWidthAlignment, mHeightAlignment);
             Range<Integer> counts = null, widths = null, heights = null;
-            Range<Integer> frameRates = null;
+            Range<Integer> frameRates = null, bitRates = null;
             Range<Long> blockRates = null;
             Range<Rational> ratios = null, blockRatios = null;
 
@@ -1148,6 +1149,16 @@
                     frameRates = null;
                 }
             }
+            bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+            if (bitRates != null) {
+                try {
+                    bitRates = bitRates.intersect(BITRATE_RANGE);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG,  "bitrate range (" + bitRates
+                            + ") is out of limits: " + BITRATE_RANGE);
+                    bitRates = null;
+                }
+            }
 
             checkPowerOfTwo(
                     blockSize.getWidth(), "block-size width must be power of two");
@@ -1196,6 +1207,9 @@
                 if (frameRates != null) {
                     mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
                 }
+                if (bitRates != null) {
+                    mBitrateRange = BITRATE_RANGE.intersect(bitRates);
+                }
             } else {
                 // no unsupported profile/levels, so restrict values to known limits
                 if (widths != null) {
@@ -1226,6 +1240,9 @@
                 if (frameRates != null) {
                     mFrameRateRange = mFrameRateRange.intersect(frameRates);
                 }
+                if (bitRates != null) {
+                    mBitrateRange = mBitrateRange.intersect(bitRates);
+                }
             }
             updateLimits();
         }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 3212eec..0dfe1dc 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -370,6 +370,7 @@
             final Runnable finishListener) {
         if (appearing) {
             animatedCell.scale = 0.0f;
+            animatedCell.alpha = 1.0f;
         }
         animatedCell.translateY = appearing ? translationY : 0;
         ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY,
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
index 5350e5a..1e2a233 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -81,11 +81,11 @@
     SecurityMode getSecurityMode() {
         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
         SecurityMode mode = SecurityMode.None;
-        if (monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED)
-                != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        if (SubscriptionManager.isValidSubscriptionId(
+                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
             mode = SecurityMode.SimPin;
-        } else if (monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED)
-                != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+        } else if (SubscriptionManager.isValidSubscriptionId(
+                    monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))
                 && mLockPatternUtils.isPukUnlockScreenEnable()) {
             mode = SecurityMode.SimPuk;
         } else {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
index f1c4cd4..f4acff8 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
@@ -75,7 +75,7 @@
         if (DEBUG) Log.v(TAG, "Resetting state");
         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
         mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
-        if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
             int count = TelephonyManager.getDefault().getSimCount();
             Resources rez = getResources();
             final String msg;
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index c9670bb..b85d966 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -118,7 +118,7 @@
             state = ENTER_PUK;
             KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
             mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
-            if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
                 int count = TelephonyManager.getDefault().getSimCount();
                 Resources rez = getResources();
                 final String msg;
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8458ae0..c8cfe31 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -938,7 +938,7 @@
                     + slotId + ", state=" + state +")");
         }
 
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             Log.w(TAG, "invalid subId in handleSimStateChange()");
             return;
         }
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
index 48bb213..5a96dc3 100644
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ b/packages/SystemUI/res/layout/qs_detail_header.xml
@@ -14,7 +14,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.keyguard.AlphaOptimizedLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_alignParentBottom="true"
@@ -36,4 +37,4 @@
         android:clickable="false"
         android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
 
-</LinearLayout>
\ No newline at end of file
+</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index f61a43c..c22e42c 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -49,4 +49,8 @@
             android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
     </LinearLayout>
 
+    <View
+        android:id="@+id/min_height_spacer"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"/>
 </com.android.systemui.qs.QSDetailItems>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index c76d442..1873168 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout
+<com.android.systemui.qs.QSContainer
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/quick_settings_container"
         android:layout_width="match_parent"
@@ -28,4 +28,4 @@
             android:background="#0000"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
-</FrameLayout>
+</com.android.systemui.qs.QSContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 2fb7cdbf..f717ac7 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -154,7 +154,7 @@
         android:layout_alignParentBottom="true"
         />
 
-    <ImageView
+    <com.android.systemui.statusbar.AlphaOptimizedImageView
         android:id="@+id/qs_detail_header_progress"
         android:src="@drawable/indeterminate_anim"
         android:alpha="0"
diff --git a/packages/SystemUI/res/values-h560dp/config.xml b/packages/SystemUI/res/values-h560dp/config.xml
new file mode 100644
index 0000000..f210d7b
--- /dev/null
+++ b/packages/SystemUI/res/values-h560dp/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <!-- The maximum number of items to be displayed in quick settings -->
+    <integer name="quick_settings_detail_max_item_count">8</integer>
+</resources>
+
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4d76f38..5b18b24 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -131,6 +131,9 @@
     <integer name="quick_settings_brightness_dialog_short_timeout">2000</integer>
     <integer name="quick_settings_brightness_dialog_long_timeout">4000</integer>
 
+    <!-- The maximum number of items to be displayed in quick settings -->
+    <integer name="quick_settings_detail_max_item_count">7</integer>
+
     <integer name="blinds_pop_duration_ms">10</integer>
 
     <!-- Should "4G" be shown instead of "LTE" when the network is NETWORK_TYPE_LTE? -->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7ac0daf..e66934e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -964,11 +964,10 @@
 
         // if the setup wizard hasn't run yet, don't show
         final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
-        final boolean absent = mUpdateMonitor.getNextSubIdForState(
-                IccCardConstants.State.ABSENT) != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        final boolean disabled = mUpdateMonitor.getNextSubIdForState(
-                IccCardConstants.State.PERM_DISABLED)
-                != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        final boolean absent = SubscriptionManager.isValidSubscriptionId(
+                mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.ABSENT));
+        final boolean disabled = SubscriptionManager.isValidSubscriptionId(
+                mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED));
         final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
                 || ((absent || disabled) && requireSim);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
new file mode 100644
index 0000000..cfe8d07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * Wrapper view with background which contains {@link QSPanel}
+ */
+public class QSContainer extends FrameLayout {
+
+    private int mHeightOverride = -1;
+    private QSPanel mQSPanel;
+
+    public QSContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        updateBottom();
+    }
+
+    /**
+     * Overrides the height of this view (post-layout), so that the content is clipped to that
+     * height and the background is set to that height.
+     *
+     * @param heightOverride the overridden height
+     */
+    public void setHeightOverride(int heightOverride) {
+        mHeightOverride = heightOverride;
+        updateBottom();
+    }
+
+    /**
+     * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
+     * during closing the detail panel, this already returns the smaller height.
+     */
+    public int getDesiredHeight() {
+        if (mQSPanel.isClosingDetail()) {
+            return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+        } else {
+            return getMeasuredHeight();
+        }
+    }
+
+    private void updateBottom() {
+        int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        setBottom(getTop() + height);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index a311d6e..9155102 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -51,8 +51,10 @@
     private boolean mItemsVisible = true;
     private LinearLayout mItems;
     private View mEmpty;
+    private View mMinHeightSpacer;
     private TextView mEmptyText;
     private ImageView mEmptyIcon;
+    private int mMaxItems;
 
     public QSDetailItems(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -77,6 +79,12 @@
         mEmpty.setVisibility(GONE);
         mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
         mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+        mMinHeightSpacer = findViewById(R.id.min_height_spacer);
+
+        // By default, a detail item view has fixed size.
+        mMaxItems = getResources().getInteger(
+                R.integer.quick_settings_detail_max_item_count);
+        setMinHeightInItems(mMaxItems);
     }
 
     @Override
@@ -102,6 +110,16 @@
         mEmptyText.setText(text);
     }
 
+    /**
+     * Set the minimum height of this detail view, in item count.
+     */
+    public void setMinHeightInItems(int minHeightInItems) {
+        ViewGroup.LayoutParams lp = mMinHeightSpacer.getLayoutParams();
+        lp.height = minHeightInItems * getResources().getDimensionPixelSize(
+                R.dimen.qs_detail_item_height);
+        mMinHeightSpacer.setLayoutParams(lp);
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -135,7 +153,7 @@
     }
 
     private void handleSetItems(Item[] items) {
-        final int itemCount = items != null ? items.length : 0;
+        final int itemCount = items != null ? Math.min(items.length, mMaxItems) : 0;
         mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
         mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
         for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 91b1569..974235e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -67,8 +67,10 @@
     private int mPanelPaddingBottom;
     private int mDualTileUnderlap;
     private int mBrightnessPaddingTop;
+    private int mGridHeight;
     private boolean mExpanded;
     private boolean mListening;
+    private boolean mClosingDetail;
 
     private Record mDetailRecord;
     private Callback mCallback;
@@ -320,6 +322,14 @@
         showDetail(false, mDetailRecord);
     }
 
+    public boolean isClosingDetail() {
+        return mClosingDetail;
+    }
+
+    public int getGridHeight() {
+        return mGridHeight;
+    }
+
     private void handleShowDetail(Record r, boolean show) {
         if (r instanceof TileRecord) {
             handleShowDetailTile((TileRecord) r, show);
@@ -364,6 +374,7 @@
             setDetailRecord(r);
             listener = mHideGridContentWhenDone;
         } else {
+            mClosingDetail = true;
             setGridContentVisibility(true);
             listener = mTeardownDetailWhenDone;
             fireScanStateChanged(false);
@@ -426,6 +437,7 @@
         if (mDetail.getMeasuredHeight() < h) {
             mDetail.measure(exactly(width), exactly(h));
         }
+        mGridHeight = h;
         setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
     }
 
@@ -537,6 +549,7 @@
         public void onAnimationEnd(Animator animation) {
             mDetailContent.removeAllViews();
             setDetailRecord(null);
+            mClosingDetail = false;
         };
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 1bc1d77..c15566f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -185,6 +185,7 @@
             mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
                     R.string.quick_settings_bluetooth_detail_empty_text);
             mItems.setCallback(this);
+            mItems.setMinHeightInItems(0);
             updateItems();
             setItemsVisible(mState.value);
             return mItems;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 09a6ccc..3e6611a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -111,14 +111,23 @@
         public void run() {
             RecentsConfiguration config = RecentsConfiguration.getInstance();
             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+                ActivityManager.RunningTaskInfo runningTaskInfo = getTopMostTask();
+
                 // Load the next task only if we aren't svelte
                 RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
                 loader.preloadTasks(plan, true /* isTopTaskHome */);
                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-                launchOpts.numVisibleTasks = 1;
-                launchOpts.numVisibleTaskThumbnails = 1;
+                // This callback is made when a new activity is launched and the old one is paused
+                // so ignore the current activity and try and preload the thumbnail for the
+                // previous one.
+                if (runningTaskInfo != null) {
+                    launchOpts.runningTaskId = runningTaskInfo.id;
+                }
+                launchOpts.numVisibleTasks = 2;
+                launchOpts.numVisibleTaskThumbnails = 2;
                 launchOpts.onlyLoadForCache = true;
+                launchOpts.onlyLoadPausedActivities = true;
                 loader.loadTasks(mContext, plan, launchOpts);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 0011811..0e1c01a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -50,6 +50,7 @@
         public boolean loadIcons = true;
         public boolean loadThumbnails = true;
         public boolean onlyLoadForCache = false;
+        public boolean onlyLoadPausedActivities = false;
         public int numVisibleTasks = 0;
         public int numVisibleTaskThumbnails = 0;
     }
@@ -141,6 +142,7 @@
                     activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
                     iconFilename);
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
+            if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
             loadedTasks.add(task);
         }
         mStack.setTasks(loadedTasks);
@@ -186,6 +188,11 @@
             boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
             boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
 
+            // If requested, skip the running task
+            if (opts.onlyLoadPausedActivities && isRunningTask) {
+                continue;
+            }
+
             if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
                 if (task.activityIcon == null) {
                     if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
@@ -194,7 +201,7 @@
                 }
             }
             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
-                if (task.thumbnail == null) {
+                if (task.thumbnail == null || isRunningTask) {
                     if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
                     if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
                         task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 7b60307..465a141 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -25,8 +25,6 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -43,7 +41,6 @@
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
@@ -355,11 +352,14 @@
             if (mActivated) {
                 mBackgroundDimmed.setVisibility(View.VISIBLE);
                 mBackgroundNormal.setVisibility(View.VISIBLE);
-            } else {
+            } else if (mDimmed) {
                 mBackgroundDimmed.setVisibility(View.VISIBLE);
                 mBackgroundNormal.setVisibility(View.INVISIBLE);
+            } else {
+                mBackgroundDimmed.setVisibility(View.INVISIBLE);
+                mBackgroundNormal.setVisibility(View.VISIBLE);
             }
-            fadeDarkToDimmed(delay);
+            fadeInFromDark(delay);
         } else {
             updateBackground();
         }
@@ -401,15 +401,16 @@
     }
 
     /**
-     * Fades the dimmed background when exiting dark mode.
+     * Fades in the background when exiting dark mode.
      */
-    private void fadeDarkToDimmed(long delay) {
-        mBackgroundDimmed.setAlpha(0f);
-        mBackgroundDimmed.setPivotX(mBackgroundDimmed.getWidth() / 2f);
-        mBackgroundDimmed.setPivotY(getActualHeight() / 2f);
-        mBackgroundDimmed.setScaleX(DARK_EXIT_SCALE_START);
-        mBackgroundDimmed.setScaleY(DARK_EXIT_SCALE_START);
-        mBackgroundDimmed.animate()
+    private void fadeInFromDark(long delay) {
+        final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
+        background.setAlpha(0f);
+        background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
+        background.setPivotY(getActualHeight() / 2f);
+        background.setScaleX(DARK_EXIT_SCALE_START);
+        background.setScaleY(DARK_EXIT_SCALE_START);
+        background.animate()
                 .alpha(1f)
                 .scaleX(1f)
                 .scaleY(1f)
@@ -420,9 +421,9 @@
                     @Override
                     public void onAnimationCancel(Animator animation) {
                         // Jump state if we are cancelled
-                        mBackgroundDimmed.setScaleX(1f);
-                        mBackgroundDimmed.setScaleY(1f);
-                        mBackgroundDimmed.setAlpha(1f);
+                        background.setScaleX(1f);
+                        background.setScaleY(1f);
+                        background.setAlpha(1f);
                     }
                 })
                 .start();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
new file mode 100644
index 0000000..094161d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * An ImageView which does not have overlapping rendering commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedImageView extends ImageView
+{
+    public AlphaOptimizedImageView(Context context) {
+        super(context);
+    }
+
+    public AlphaOptimizedImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlphaOptimizedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlphaOptimizedImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d2dc425..1b00e59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -40,6 +40,7 @@
 
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
+import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -71,7 +72,7 @@
     private StatusBarHeaderView mHeader;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
-    private View mQsContainer;
+    private QSContainer mQsContainer;
     private QSPanel mQsPanel;
     private KeyguardStatusView mKeyguardStatusView;
     private ObservableScrollView mScrollView;
@@ -161,6 +162,7 @@
     private boolean mKeyguardStatusViewAnimating;
     private boolean mHeaderAnimatingIn;
     private ObjectAnimator mQsContainerAnimator;
+    private ValueAnimator mQsSizeChangeAnimator;
 
     private boolean mShadeEmpty;
 
@@ -188,7 +190,7 @@
         mHeader.setOnClickListener(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
-        mQsContainer = findViewById(R.id.quick_settings_container);
+        mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
         mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
@@ -283,21 +285,35 @@
         mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
 
         // Calculate quick setting heights.
+        int oldMaxHeight = mQsMaxExpansionHeight;
         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
-        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
+        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
         positionClockAndNotifications();
-        if (mQsExpanded) {
-            if (mQsFullyExpanded) {
-                mQsExpansionHeight = mQsMaxExpansionHeight;
-                requestScrollerTopPaddingUpdate(false /* animate */);
+        if (mQsExpanded && mQsFullyExpanded) {
+            mQsExpansionHeight = mQsMaxExpansionHeight;
+            requestScrollerTopPaddingUpdate(false /* animate */);
+            requestPanelHeightUpdate();
+
+            // Size has changed, start an animation.
+            if (mQsMaxExpansionHeight != oldMaxHeight) {
+                startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
             }
-        } else {
+        } else if (!mQsExpanded) {
             setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
-            mNotificationStackScroller.setStackHeight(getExpandedHeight());
         }
+        mNotificationStackScroller.setStackHeight(getExpandedHeight());
         updateHeader();
         mNotificationStackScroller.updateIsSmallScreen(
                 mHeader.getCollapsedHeight() + mQsPeekHeight);
+
+        // If we are running a size change animation, the animation takes care of the height of
+        // the container. However, if we are not animating, we always need to make the QS container
+        // the desired height so when closing the QS detail, it stays smaller after the size change
+        // animation is finished but the detail view is still being animated away (this animation
+        // takes longer than the size change animation).
+        if (mQsSizeChangeAnimator == null) {
+            mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
+        }
     }
 
     @Override
@@ -310,6 +326,32 @@
         mSecureCameraLaunchManager.destroy();
     }
 
+    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+        if (mQsSizeChangeAnimator != null) {
+            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+            mQsSizeChangeAnimator.cancel();
+        }
+        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+        mQsSizeChangeAnimator.setDuration(300);
+        mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
+        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+                mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
+            }
+        });
+        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mQsSizeChangeAnimator = null;
+            }
+        });
+        mQsSizeChangeAnimator.start();
+    }
+
     /**
      * Positions the clock and notifications dynamically depending on how many notifications are
      * showing.
@@ -1113,7 +1155,7 @@
 
     private void setQsTranslation(float height) {
         if (!mHeaderAnimatingIn) {
-            mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
+            mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
         }
         if (mKeyguardShowing) {
             mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
@@ -1137,6 +1179,8 @@
                     : maxQs;
             return (int) interpolate(getExpandedFraction(),
                     mQsMinExpansionHeight, max);
+        } else if (mQsSizeChangeAnimator != null) {
+            return (int) mQsSizeChangeAnimator.getAnimatedValue();
         } else if (mKeyguardShowing && mScrollYOverride == -1) {
 
             // We can only do the smoother transition on Keyguard when we also are not collapsing
@@ -1348,14 +1392,20 @@
                     + mNotificationStackScroller.getBottomStackPeekSize()
                     + mNotificationStackScroller.getCollapseSecondCardPadding();
         }
+        int maxQsHeight = mQsMaxExpansionHeight;
+
+        // If an animation is changing the size of the QS panel, take the animated value.
+        if (mQsSizeChangeAnimator != null) {
+            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+        }
         float totalHeight = Math.max(
-                mQsMaxExpansionHeight + mNotificationStackScroller.getNotificationTopPadding(),
+                maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
                 mStatusBarState == StatusBarState.KEYGUARD
                         ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
                         : 0)
                 + notificationHeight;
         if (totalHeight > mNotificationStackScroller.getHeight()) {
-            float fullyCollapsedHeight = mQsMaxExpansionHeight
+            float fullyCollapsedHeight = maxQsHeight
                     + mNotificationStackScroller.getMinStackHeight()
                     + mNotificationStackScroller.getNotificationTopPadding()
                     - getScrollViewScrollY();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 247252c..181926c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -777,9 +777,11 @@
                 v.bringToFront();
                 v.setVisibility(VISIBLE);
             }
+            if (v.hasOverlappingRendering()) {
+                v.animate().withLayer();
+            }
             v.animate()
                     .alpha(in ? 1 : 0)
-                    .withLayer()
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 076cfe2..229c558 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -289,9 +289,11 @@
                         .getDevicesMatchingConnectionStates(connectionType);
                 for (int k = 0; k < devices.size(); k++) {
                     DeviceInfo info = mDeviceInfo.get(devices.get(k));
-                    info.connectionState = CONNECTION_STATES[i];
-                    if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) {
-                        info.connectedProfiles.put(profile, true);
+                    if (info != null) {
+                        info.connectionState = CONNECTION_STATES[i];
+                        if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) {
+                            info.connectedProfiles.put(profile, true);
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 3397a38..81c6da5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -21,7 +21,6 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +48,7 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants;
@@ -92,7 +92,7 @@
     private final ConnectivityManager mConnectivityManager;
     private final SubscriptionManager mSubscriptionManager;
     private final boolean mHasMobileDataFeature;
-    private final Config mConfig;
+    private Config mConfig;
 
     // Subcontrollers.
     @VisibleForTesting
@@ -265,7 +265,7 @@
 
     private MobileSignalController getDataController() {
         int dataSubId = SubscriptionManager.getDefaultDataSubId();
-        if (dataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        if (!SubscriptionManager.isValidSubscriptionId(dataSubId)) {
             if (DEBUG) Log.e(TAG, "No data sim selected");
             return mDefaultSignalController;
         }
@@ -283,8 +283,9 @@
 
     public boolean isEmergencyOnly() {
         int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
-        if (voiceSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+        if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
+            for (MobileSignalController mobileSignalController :
+                                            mMobileSignalControllers.values()) {
                 if (!mobileSignalController.isEmergencyOnly()) {
                     return false;
                 }
@@ -376,8 +377,8 @@
             updateConnectivity();
             refreshCarrierLabel();
         } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
-            refreshLocale();
-            refreshCarrierLabel();
+            mConfig = Config.readConfig(mContext);
+            handleConfigurationChanged();
         } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
             refreshLocale();
             updateAirplaneMode(false);
@@ -397,7 +398,7 @@
         } else {
             int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                 if (mMobileSignalControllers.containsKey(subId)) {
                     mMobileSignalControllers.get(subId).handleBroadcast(intent);
                 } else {
@@ -411,6 +412,15 @@
         }
     }
 
+    @VisibleForTesting
+    void handleConfigurationChanged() {
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.setConfiguration(mConfig);
+        }
+        refreshLocale();
+        refreshCarrierLabel();
+    }
+
     private void updateMobileControllers() {
         if (!mListening) {
             return;
@@ -982,7 +992,6 @@
     // TODO: Move to its own file.
     static class MobileSignalController extends SignalController<MobileSignalController.MobileState,
             MobileSignalController.MobileIconGroup> {
-        private final Config mConfig;
         private final TelephonyManager mPhone;
         private final String mNetworkNameDefault;
         private final String mNetworkNameSeparator;
@@ -992,7 +1001,7 @@
         private final SubscriptionInfo mSubscriptionInfo;
 
         // @VisibleForDemoMode
-        Map<Integer, MobileIconGroup> mNetworkToIconLookup;
+        final SparseArray<MobileIconGroup> mNetworkToIconLookup;
 
         // Since some pieces of the phone state are interdependent we store it locally,
         // this could potentially become part of MobileState for simplification/complication
@@ -1003,6 +1012,7 @@
         private ServiceState mServiceState;
         private SignalStrength mSignalStrength;
         private MobileIconGroup mDefaultIcons;
+        private Config mConfig;
 
         // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
         // need listener lists anymore.
@@ -1013,6 +1023,7 @@
             super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
                     NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
                     networkController);
+            mNetworkToIconLookup = new SparseArray<>();
             mConfig = config;
             mPhone = phone;
             mSubscriptionInfo = info;
@@ -1030,6 +1041,12 @@
             updateDataSim();
         }
 
+        public void setConfiguration(Config config) {
+            mConfig = config;
+            mapIconSets();
+            updateTelephony();
+        }
+
         /**
          * Get (the mobile parts of) the carrier string.
          *
@@ -1114,12 +1131,9 @@
         /**
          * Produce a mapping of data network types to icon groups for simple and quick use in
          * updateTelephony.
-         *
-         * TODO: See if config can change with locale, this may need to be regenerated on Locale
-         * change.
          */
         private void mapIconSets() {
-            mNetworkToIconLookup = new HashMap<Integer, MobileIconGroup>();
+            mNetworkToIconLookup.clear();
 
             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
@@ -1265,7 +1279,7 @@
 
         private void updateDataSim() {
             int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
-            if (SubscriptionManager.isValidSubId(defaultDataSub)) {
+            if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
                 mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
             } else {
                 // There doesn't seem to be a data sim selected, however if
@@ -1323,7 +1337,7 @@
                     mCurrentState.level = mSignalStrength.getLevel();
                 }
             }
-            if (mNetworkToIconLookup.containsKey(mDataNetType)) {
+            if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
                 mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
             } else {
                 mCurrentState.iconGroup = mDefaultIcons;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 2a393bf..6dcbed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2280,8 +2280,7 @@
                 updateContentHeight();
                 notifyHeightChangeListener(mDismissView);
             } else {
-                mEmptyShadeView.setWillBeGone(true);
-                mEmptyShadeView.performVisibilityAnimation(false, new Runnable() {
+                Runnable onFinishedRunnable = new Runnable() {
                     @Override
                     public void run() {
                         mEmptyShadeView.setVisibility(GONE);
@@ -2289,7 +2288,14 @@
                         updateContentHeight();
                         notifyHeightChangeListener(mDismissView);
                     }
-                });
+                };
+                if (mAnimationsEnabled) {
+                    mEmptyShadeView.setWillBeGone(true);
+                    mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
+                } else {
+                    mEmptyShadeView.setInvisible();
+                    onFinishedRunnable.run();
+                }
             }
         }
     }
@@ -2318,7 +2324,7 @@
                         notifyHeightChangeListener(mDismissView);
                     }
                 };
-                if (mDismissView.isButtonVisible() && mIsExpanded) {
+                if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
                     mDismissView.setWillBeGone(true);
                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
                 } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 020bc00..d85b059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -4,9 +4,17 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
 import android.content.Intent;
 import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
+import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
@@ -22,14 +30,6 @@
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
 
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-
 public class NetworkControllerBaseTest extends AndroidTestCase {
     private static final String TAG = "NetworkControllerBaseTest";
     protected static final int DEFAULT_LEVEL = 2;
@@ -52,6 +52,8 @@
     protected TelephonyManager mMockTm;
     protected Config mConfig;
 
+    private NetworkCapabilities mNetCapabilities;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -63,7 +65,10 @@
         mMockTm = mock(TelephonyManager.class);
         mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
+        mNetCapabilities = new NetworkCapabilities();
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
+        when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
+                new NetworkCapabilities[] { mNetCapabilities });
 
         mSignalStrength = mock(SignalStrength.class);
         mServiceState = mock(ServiceState.class);
@@ -115,13 +120,18 @@
 
     public void setConnectivity(int inetCondition, int networkType, boolean isConnected) {
         Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
-        NetworkInfo networkInfo = mock(NetworkInfo.class);
-        when(networkInfo.isConnected()).thenReturn(isConnected);
-        when(networkInfo.getType()).thenReturn(networkType);
-        when(networkInfo.getTypeName()).thenReturn("");
-        when(mMockCm.getActiveNetworkInfo()).thenReturn(networkInfo);
+        // TODO: Separate out into several NetworkCapabilities.
+        if (isConnected) {
+            mNetCapabilities.addTransportType(networkType);
+        } else {
+            mNetCapabilities.removeTransportType(networkType);
+        }
+        if (inetCondition != 0) {
+            mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        } else {
+            mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        }
 
-        i.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, inetCondition);
         mNetworkController.onReceive(mContext, i);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index e327233..3f9312d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,9 +1,9 @@
 package com.android.systemui.statusbar.policy;
 
+import org.mockito.Mockito;
+
 import android.telephony.TelephonyManager;
 
-// WARNING: Many of these tests may fail with config showMin3G.
-// TODO: Maybe fix the above.
 public class NetworkControllerDataTest extends NetworkControllerBaseTest {
 
     public void test3gDataIcon() {
@@ -57,7 +57,6 @@
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
 
-        // WARNING: May fail depending on config.
         verifyDataIndicators(TelephonyIcons.DATA_LTE[1][0 /* No direction */],
                 TelephonyIcons.QS_DATA_LTE[1]);
     }
@@ -67,11 +66,42 @@
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_HSPA);
 
-        // WARNING: May fail depending on config.
         verifyDataIndicators(TelephonyIcons.DATA_H[1][0 /* No direction */],
                 TelephonyIcons.QS_DATA_H[1]);
     }
 
+    public void test4gDataIcon() {
+        // Switch to showing 4g icon and re-initialize the NetworkController.
+        mConfig.show4gForLte = true;
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+                mConfig, Mockito.mock(AccessPointControllerImpl.class),
+                Mockito.mock(MobileDataControllerImpl.class));
+        setupNetworkController();
+
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.DATA_4G[1][0 /* No direction */],
+                TelephonyIcons.QS_DATA_4G[1]);
+    }
+
+    public void test4gDataIconConfigChange() {
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Switch to showing 4g icon and re-initialize the NetworkController.
+        mConfig.show4gForLte = true;
+        // Can't send the broadcast as that would actually read the config from
+        // the context.  Instead we'll just poke at a function that does all of
+        // the after work.
+        mNetworkController.handleConfigurationChanged();
+
+        verifyDataIndicators(TelephonyIcons.DATA_4G[1][0 /* No direction */],
+                TelephonyIcons.QS_DATA_4G[1]);
+    }
+
     public void testDataActivity() {
         setupDefaultSignal();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 33eb4d6..27a4052 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -60,7 +60,7 @@
                     DEFAULT_ICON);
 
             // Verify low inet number indexing.
-            setConnectivity(10, ConnectivityManager.TYPE_MOBILE, true);
+            setConnectivity(0, ConnectivityManager.TYPE_MOBILE, true);
             verifyLastMobileDataIndicators(true,
                     TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[0][testStrength], 0);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 7f0a8f4..2e0e9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -27,7 +27,7 @@
 
             setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
             verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
-            setConnectivity(10, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(0, ConnectivityManager.TYPE_WIFI, true);
             verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
         }
     }
@@ -48,7 +48,7 @@
             setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel],
                     testSsid);
-            setConnectivity(10, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(0, ConnectivityManager.TYPE_WIFI, true);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
                     testSsid);
         }
@@ -75,22 +75,6 @@
         verifyLastQsDataDirection(true, true);
     }
 
-    public void testNoDataIconDuringWifi() {
-        // Setup normal connection
-        String testSsid = "Test SSID";
-        int testLevel = 2;
-        setWifiEnabled(true);
-        setWifiState(true, testSsid);
-        setWifiLevel(testLevel);
-        setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
-        verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
-
-        setupDefaultSignal();
-        // Still be on wifi though.
-        setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
-        verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0 /* No icon */);
-    }
-
     public void testRoamingIconDuringWifi() {
         // Setup normal connection
         String testSsid = "Test SSID";
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 6c5c508..541cce8 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -1184,6 +1184,13 @@
             mApplicationContext = ctx.getApplicationContext();
         }
         mRWLock = new ReentrantReadWriteLock();
+        try {
+            registerNativeAllocation.invoke(sRuntime, 4 * 1024 * 1024 * 1024); // 4MB for GC sake
+        } catch (Exception e) {
+            Log.e(RenderScript.LOG_TAG, "Couldn't invoke registerNativeAllocation:" + e);
+            throw new RSRuntimeException("Couldn't invoke registerNativeAllocation:" + e);
+        }
+
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63a0cf6..5ae26ef 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2145,6 +2145,9 @@
                 if (!permissionGranted) {
                     return null;
                 }
+                if (mSecurityPolicy.mWindows == null) {
+                    return null;
+                }
                 List<AccessibilityWindowInfo> windows = new ArrayList<>();
                 final int windowCount = mSecurityPolicy.mWindows.size();
                 for (int i = 0; i < windowCount; i++) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 34da901..776f836 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -480,7 +480,7 @@
                 r.callerUid = callerUid;
                 // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
                 // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
-                if (!SubscriptionManager.isValidSubId(subId)) {
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                     r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
                  } else {//APP specify subID
                     r.subId = subId;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 31d5cd7..0f9a59b 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -347,7 +347,10 @@
                 break;
             case AudioManager.RINGER_MODE_VIBRATE:
             case AudioManager.RINGER_MODE_NORMAL:
-                if (mZenMode != Global.ZEN_MODE_OFF) {
+                if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+                        && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+                    newZen = Global.ZEN_MODE_OFF;
+                } else if (mZenMode != Global.ZEN_MODE_OFF) {
                     ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
                 }
                 break;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 257cbd0..bd2e923 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -116,6 +116,7 @@
         final WallpaperData mWallpaper;
         final File mWallpaperDir;
         final File mWallpaperFile;
+        final File mWallpaperInfoFile;
 
         public WallpaperObserver(WallpaperData wallpaper) {
             super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
@@ -123,6 +124,7 @@
             mWallpaperDir = getWallpaperDir(wallpaper.userId);
             mWallpaper = wallpaper;
             mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
+            mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
         }
 
         @Override
@@ -131,13 +133,15 @@
                 return;
             }
             synchronized (mLock) {
-                // changing the wallpaper means we'll need to back up the new one
-                long origId = Binder.clearCallingIdentity();
-                BackupManager bm = new BackupManager(mContext);
-                bm.dataChanged();
-                Binder.restoreCallingIdentity(origId);
-
                 File changedFile = new File(mWallpaperDir, path);
+                if (mWallpaperFile.equals(changedFile)
+                        || mWallpaperInfoFile.equals(changedFile)) {
+                    // changing the wallpaper means we'll need to back up the new one
+                    long origId = Binder.clearCallingIdentity();
+                    BackupManager bm = new BackupManager(mContext);
+                    bm.dataChanged();
+                    Binder.restoreCallingIdentity(origId);
+                }
                 if (mWallpaperFile.equals(changedFile)) {
                     notifyCallbacksLocked(mWallpaper);
                     final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index fe2e0a6..64713d9 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -355,7 +355,8 @@
                         boolean applyExistingExitAnimation = mPostKeyguardExitAnimation != null
                                 && !winAnimator.mKeyguardGoingAwayAnimation
                                 && win.hasDrawnLw()
-                                && win.mAttachedWindow == null;
+                                && win.mAttachedWindow == null
+                                && mForceHiding != KEYGUARD_NOT_SHOWN;
 
                         // If the window is already showing and we don't need to apply an existing
                         // Keyguard exit animation, skip.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 39cce69..d174f47 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -392,7 +392,7 @@
      */
     public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
         if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId);
-        if (!isValidSubId(subId)) {
+        if (!isValidSubscriptionId(subId)) {
             logd("[getActiveSubscriptionInfo]- invalid subId");
             return null;
         }
@@ -626,7 +626,7 @@
      */
     public int setIconTint(int tint, int subId) {
         if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
-        if (!isValidSubId(subId)) {
+        if (!isValidSubscriptionId(subId)) {
             logd("[setIconTint]- fail");
             return -1;
         }
@@ -671,7 +671,7 @@
             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
                     + " nameSource:" + nameSource);
         }
-        if (!isValidSubId(subId)) {
+        if (!isValidSubscriptionId(subId)) {
             logd("[setDisplayName]- fail");
             return -1;
         }
@@ -699,7 +699,7 @@
      * @hide
      */
     public int setDisplayNumber(String number, int subId) {
-        if (number == null || !isValidSubId(subId)) {
+        if (number == null || !isValidSubscriptionId(subId)) {
             logd("[setDisplayNumber]- fail");
             return -1;
         }
@@ -728,7 +728,7 @@
      */
     public int setDataRoaming(int roaming, int subId) {
         if (VDBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
-        if (roaming < 0 || !isValidSubId(subId)) {
+        if (roaming < 0 || !isValidSubscriptionId(subId)) {
             logd("[setDataRoaming]- fail");
             return -1;
         }
@@ -754,7 +754,7 @@
      * @hide
      */
     public static int getSlotId(int subId) {
-        if (!isValidSubId(subId)) {
+        if (!isValidSubscriptionId(subId)) {
             logd("[getSlotId]- fail");
         }
 
@@ -796,7 +796,7 @@
 
     /** @hide */
     public static int getPhoneId(int subId) {
-        if (!isValidSubId(subId)) {
+        if (!isValidSubscriptionId(subId)) {
             logd("[getPhoneId]- fail");
             return INVALID_PHONE_INDEX;
         }
@@ -985,13 +985,13 @@
     //FIXME this is vulnerable to race conditions
     /** @hide */
     public boolean allDefaultsSelected() {
-        if (getDefaultDataSubId() == INVALID_SUBSCRIPTION_ID) {
+        if (!isValidSubscriptionId(getDefaultDataSubId())) {
             return false;
         }
-        if (getDefaultSmsSubId() == INVALID_SUBSCRIPTION_ID) {
+        if (!isValidSubscriptionId(getDefaultSmsSubId())) {
             return false;
         }
-        if (getDefaultVoiceSubId() == INVALID_SUBSCRIPTION_ID) {
+        if (!isValidSubscriptionId(getDefaultVoiceSubId())) {
             return false;
         }
         return true;
@@ -1018,7 +1018,7 @@
      * @return true if a valid subId else false
      * @hide
      */
-    public static boolean isValidSubId(int subId) {
+    public static boolean isValidSubscriptionId(int subId) {
         return subId > INVALID_SUBSCRIPTION_ID ;
     }
 
diff --git a/tests/utils/Android.mk b/tests/utils/Android.mk
new file mode 100644
index 0000000..c141484
--- /dev/null
+++ b/tests/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/tests/utils/DummyIME/Android.mk b/tests/utils/DummyIME/Android.mk
new file mode 100644
index 0000000..c8d9f87
--- /dev/null
+++ b/tests/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/tests/utils/DummyIME/AndroidManifest.xml b/tests/utils/DummyIME/AndroidManifest.xml
new file mode 100644
index 0000000..fd17a52
--- /dev/null
+++ b/tests/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/tests/utils/DummyIME/res/xml/method.xml b/tests/utils/DummyIME/res/xml/method.xml
new file mode 100644
index 0000000..43a330e
--- /dev/null
+++ b/tests/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/tests/utils/DummyIME/src/com/android/testing/dummyime/DummyIme.java b/tests/utils/DummyIME/src/com/android/testing/dummyime/DummyIme.java
new file mode 100644
index 0000000..7b7a39a
--- /dev/null
+++ b/tests/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/tests/utils/DummyIME/src/com/android/testing/dummyime/ImePreferences.java b/tests/utils/DummyIME/src/com/android/testing/dummyime/ImePreferences.java
new file mode 100644
index 0000000..41036ab
--- /dev/null
+++ b/tests/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 {
+
+}
diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk
new file mode 100644
index 0000000..9022f03
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += \
+    src/com/android/testing/alarmservice/Alarm.aidl
+LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
+LOCAL_SDK_VERSION := 7
+include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
new file mode 100644
index 0000000..1b6de39
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
+    Apache License, Version 2.0 (the "License"); you may not use this file except
+    in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software distributed
+    under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+    OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+    the specific language governing permissions and limitations under the License. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.testing.alarmservice" >
+
+    <uses-sdk android:minSdkVersion="7" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <application android:label="Sleep Utils Alarm Service">
+        <service android:name=".AlarmService"
+            android:label="Sleep Utils Alarm Service"
+            android:exported="true"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="com.android.testing.ALARM_SERVICE" />
+            </intent-filter>
+        </service>
+        <receiver android:name=".WakeUpCall">
+            <intent-filter>
+                <action android:name="com.android.testing.alarmservice.WAKEUP" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
new file mode 100644
index 0000000..62a8c48
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.alarmservice;
+
+interface Alarm {
+    int prepare();
+    int setAlarmAndWait(long timeoutMills);
+    int done();
+}
\ No newline at end of file
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
new file mode 100644
index 0000000..122d55d
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.alarmservice;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.testing.alarmservice.Alarm.Stub;
+
+public class AlarmImpl extends Stub {
+
+    private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
+
+    private Context mContext;
+
+    public AlarmImpl(Context context) {
+        super();
+        mContext = context;
+    }
+
+    @Override
+    public int prepare() throws RemoteException {
+        WakeUpController.getController().getWakeLock().acquire();
+        Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
+        return 0;
+    }
+
+    @Override
+    public int setAlarmAndWait(long timeoutMills) throws RemoteException {
+        // calculate when device should be waken up
+        long atTime = SystemClock.elapsedRealtime() + timeoutMills;
+        AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
+        // set alarm, which will be delivered in form of the wakeupIntent
+        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
+        Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
+        Object lock = WakeUpController.getController().getWakeSync();
+        // release wakelock and wait for the lock to be poked from the broadcast receiver
+        WakeUpController.getController().getWakeLock().release();
+        // does not really matter if device enters suspend before we start waiting on lock
+        synchronized (lock) {
+            try {
+                lock.wait();
+            } catch (InterruptedException e) {
+            }
+        }
+        Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
+        return 0;
+    }
+
+    @Override
+    public int done() throws RemoteException {
+        WakeUpController.getController().getWakeLock().release();
+        return 0;
+    }
+
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
new file mode 100644
index 0000000..576a1cf
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.alarmservice;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class AlarmService extends Service {
+
+    private AlarmImpl mAlarmImpl = null;
+    static Context sContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sContext = this;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return getAlarmImpl();
+    }
+
+    private AlarmImpl getAlarmImpl() {
+        if (mAlarmImpl == null) {
+            mAlarmImpl = new AlarmImpl(this);
+        }
+        return mAlarmImpl;
+    }
+
+    @Override
+    public void onDestroy() {
+        sContext = null;
+        super.onDestroy();
+    }
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
new file mode 100644
index 0000000..f4bb4db
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.alarmservice;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * The receiver for the alarm we set
+ *
+ */
+public class WakeUpCall extends BroadcastReceiver {
+
+    public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // we acquire wakelock without release because user is supposed to manually release it
+        WakeUpController.getController().getWakeLock().acquire();
+        Object lock = WakeUpController.getController().getWakeSync();
+        synchronized (lock) {
+            // poke the lock so the service side can be woken from waiting on the lock
+            lock.notifyAll();
+        }
+    }
+
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
new file mode 100644
index 0000000..478371f
--- /dev/null
+++ b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.alarmservice;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * A singleton used for controlling and sharing of states/wakelocks
+ *
+ */
+public class WakeUpController {
+
+    private static final String LOG_TAG = WakeUpController.class.getName();
+    private static WakeUpController mController = null;
+    private WakeLock mWakeLock = null;
+    private Object mWakeSync = new Object();
+
+    private WakeUpController() {
+        Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
+    }
+
+    public static synchronized WakeUpController getController() {
+        if (mController == null) {
+            mController = new WakeUpController();
+        }
+        return mController;
+    }
+
+    public WakeLock getWakeLock() {
+        if (mWakeLock == null) {
+            PowerManager pm =
+                    (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
+            Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
+        }
+        return mWakeLock;
+    }
+
+    public Object getWakeSync() {
+        return mWakeSync;
+    }
+}
diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk
new file mode 100644
index 0000000..0e65e22
--- /dev/null
+++ b/tests/utils/SleepUtils/Android.mk
@@ -0,0 +1,2 @@
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README
new file mode 100644
index 0000000..bfe07da
--- /dev/null
+++ b/tests/utils/SleepUtils/README
@@ -0,0 +1,23 @@
+This folder contains utils to properly perform timed suspend and wakeup.
+
+AlarmService - a service that client can bind to and perform:
+1) holding wakelock (singleton to this service)
+2) setting alarm for a specified period and releasing the wakelock; service
+   call will block until alarm has been triggered and the wakelock is held
+3) releasing the wakelock
+
+SleepHelper - a self instrumentation meant as a convenient way to trigger
+the service functions from command line. Corresponding to service function
+above, supported operations are:
+1) holding wakelock
+am instrument -w -e command prepare \
+  com.android.testing.sleephelper/.SetAlarm
+
+2) setting alarm and wait til triggered
+am instrument -w -e command set_wait \
+  -e param <time in ms> com.android.testing.sleephelper/.SetAlarm
+Note: for the function to work properly, "-w" parameter is required
+
+3) releasing wakelock
+am instrument -w -e command done \
+  com.android.testing.sleephelper/.SetAlarm
diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk
new file mode 100644
index 0000000..f8267fd
--- /dev/null
+++ b/tests/utils/SleepUtils/SleepHelper/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += \
+    ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+LOCAL_SDK_VERSION := 7
+LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
+
+include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
new file mode 100644
index 0000000..0f1d491
--- /dev/null
+++ b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
+    Apache License, Version 2.0 (the "License"); you may not use this file except
+    in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software distributed
+    under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+    OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+    the specific language governing permissions and limitations under the License. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.testing.sleephelper">
+
+    <uses-sdk android:minSdkVersion="7" />
+    <instrumentation android:label="Sleep Helper"
+                     android:name="com.android.testing.sleephelper.SetAlarm"
+                     android:targetPackage="com.android.testing.sleephelper" />
+
+    <application android:label="Sleep Utils Sleep Helper">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
new file mode 100644
index 0000000..b558741
--- /dev/null
+++ b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testing.sleephelper;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.testing.alarmservice.Alarm;
+
+public class SetAlarm extends Instrumentation {
+
+    private static final String COMMAND = "command";
+    private static final String PARAM = "param";
+    private static final String CMD_PREPARE = "prepare";
+    private static final String CMD_SET = "set_wait";
+    private static final String CMD_DONE = "done";
+    private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
+    private static final String SERVICE_PKG = "com.android.testing.alarmservice";
+    private static final String LOG_TAG = SetAlarm.class.getSimpleName();
+
+    private Alarm mAlarmService = null;
+    private Bundle mArgs = null;
+    private String mCommand = null;
+    private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
+
+    private ServiceConnection mConn = new ServiceConnection() {
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(LOG_TAG, "Service disconnected.");
+            mAlarmService = null;
+            errorFinish("service disconnected");
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(LOG_TAG, "Service connected.");
+            mAlarmService = Alarm.Stub.asInterface(service);
+            handleCommands();
+        }
+    };
+
+
+    private void handleCommands() {
+        if (CMD_PREPARE.equals(mCommand)) {
+            callPrepare();
+        } else if (CMD_SET.equals(mCommand)) {
+            String paramString = mArgs.getString(PARAM);
+            if (paramString == null) {
+                errorFinish("argument expected for alarm time");
+            }
+            long timeout = -1;
+            try {
+                timeout = Long.parseLong(paramString);
+            } catch (NumberFormatException nfe) {
+                errorFinish("a number argument is expected");
+            }
+            callSetAndWait(timeout);
+        } else if (CMD_DONE.equals(mCommand)) {
+            callDone();
+        } else {
+            errorFinish("Unrecognized command: " + mCommand);
+        }
+        finish(Activity.RESULT_OK, new Bundle());
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+        mCommand = arguments.getString(COMMAND);
+        if ("true".equals(arguments.getString("debug"))) {
+            Debug.waitForDebugger();
+        }
+        if (mCommand == null) {
+            errorFinish("No command specified");
+        }
+        mArgs = arguments;
+        connectToAlarmService();
+    }
+
+    private void errorFinish(String msg) {
+        Bundle ret = new Bundle();
+        ret.putString("error", msg);
+        finish(Activity.RESULT_CANCELED, ret);
+    }
+
+    private void connectToAlarmService() {
+        // start the service with an intent, this ensures the service keeps running after unbind
+        ComponentName cn = getContext().startService(mServceIntent);
+        if (cn == null) {
+            errorFinish("failed to start service");
+        }
+        if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
+            errorFinish("failed to bind service");
+        }
+    }
+
+    private void callPrepare() {
+        try {
+            mAlarmService.prepare();
+        } catch (RemoteException e) {
+            errorFinish("RemoteExeption in prepare()");
+        } finally {
+            getContext().unbindService(mConn);
+        }
+    }
+
+    private void callDone() {
+        try {
+            mAlarmService.done();
+        } catch (RemoteException e) {
+            errorFinish("RemoteExeption in prepare()");
+        } finally {
+            getContext().unbindService(mConn);
+        }
+        // explicitly stop the service (started in prepare()) so that the service is now free
+        // to be reclaimed
+        getContext().stopService(mServceIntent);
+    }
+
+    private void callSetAndWait(long timeoutMills) {
+        try {
+            mAlarmService.setAlarmAndWait(timeoutMills);
+        } catch (RemoteException e) {
+            errorFinish("RemoteExeption in setAlarmAndWait()");
+        } finally {
+            getContext().unbindService(mConn);
+        }
+    }
+}
diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk
new file mode 100644
index 0000000..a8a944b
--- /dev/null
+++ b/tests/utils/SleepUtils/WakeLoopService/Android.mk
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := WakeupLoopService
+LOCAL_SDK_VERSION := 7
+include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
new file mode 100644
index 0000000..a7028c4
--- /dev/null
+++ b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project Licensed under the
+    Apache License, Version 2.0 (the "License"); you may not use this file except
+    in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software distributed
+    under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+    OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+    the specific language governing permissions and limitations under the License. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.test.wakeuploop" >
+
+    <uses-sdk android:minSdkVersion="7" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application android:label="Auto Wakeup Loop">
+        <service android:name=".WakeLoopService"
+            android:label="Wakup Loop Service"
+            android:exported="true"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
+            </intent-filter>
+        </service>
+        <receiver android:name=".WakeUpCall">
+            <intent-filter>
+                <action android:name="android.test.wakeuploop.WAKEUP" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
new file mode 100644
index 0000000..c8b075b
--- /dev/null
+++ b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.wakeuploop;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class FileUtil {
+
+    private static FileUtil sInst = null;
+    private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+
+    private FileUtil() {};
+
+    public static FileUtil get() {
+        if (sInst == null) {
+            sInst = new FileUtil();
+        }
+        return sInst;
+    }
+
+    public void writeDateToFile(File file) {
+        try {
+            FileOutputStream fos = new FileOutputStream(file);
+            fos.write(sDateFormat.format(new Date()).getBytes());
+            fos.write('\n');
+            fos.flush();
+            fos.close();
+        } catch (IOException ioe) {
+            Log.e("FileUtil", "exception writing date to file", ioe);
+        }
+    }
+}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
new file mode 100644
index 0000000..4f557b8
--- /dev/null
+++ b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.wakeuploop;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.File;
+
+public class WakeLoopService extends Service {
+
+    private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
+    static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
+    static final String MAX_LOOP = "MAX_LOOP";
+    static final String STOP_CALLBACK = "STOP_CALLBACK";
+    static final String THIS_LOOP = "THIS_LOOP";
+    static final int MSG_STOP_SERVICE = 0xd1ed1e;
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_STOP_SERVICE) {
+                stopSelf();
+            } else {
+                super.handleMessage(msg);
+            }
+        };
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // no binding, just start via intent
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        // get wakeup interval from intent
+        long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
+        long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
+
+        if (wakeupInterval == 0) {
+            // stop and error
+            Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
+            stopSelf();
+            return START_NOT_STICKY;
+        }
+        FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
+                "wakeup-loop-start.txt"));
+        Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
+                wakeupInterval, maxLoop));
+        // calculate when device should be waken up
+        long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
+        AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+        Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
+            .putExtra(WAKEUP_INTERNAL, wakeupInterval)
+            .putExtra(MAX_LOOP, maxLoop)
+            .putExtra(THIS_LOOP, 0L)
+            .putExtra(STOP_CALLBACK, new Messenger(mHandler));
+        PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        // set alarm, which will be delivered in form of the wakeupIntent
+        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(LOG_TAG, "WakeLoop: STOPPED");
+        // cancel alarms first
+        Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
+            .putExtra(WakeUpCall.CANCEL, "true");
+        sendBroadcast(intent);
+    }
+}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
new file mode 100644
index 0000000..8347bbf0
--- /dev/null
+++ b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.wakeuploop;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * The receiver for the alarm we set
+ *
+ */
+public class WakeUpCall extends BroadcastReceiver {
+    private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
+    static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
+    static final String CANCEL = "CANCEL";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        boolean cancel = intent.hasExtra(CANCEL);
+        if (!cancel) {
+            long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
+            long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
+            long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
+            Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
+                    wakeupInterval, maxLoop, thisLoop));
+            if (thisLoop == -1) {
+                Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
+                stopService(intent);
+                return;
+            }
+            if (wakeupInterval == 0) {
+                Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
+                stopService(intent);
+                return;
+            }
+            thisLoop++;
+            Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
+            if (thisLoop == maxLoop) {
+                // when maxLoop is 0, we loop forever, so not checking that case
+                // here
+                Log.d(LOG_TAG, "reached max loop count, stopping service");
+                stopService(intent);
+                return;
+            }
+            screenOn(context);
+            FileUtil.get().writeDateToFile(
+                    new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
+            // calculate when device should be waken up
+            long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
+            intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
+            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+                    PendingIntent.FLAG_UPDATE_CURRENT);
+            // set alarm, which will be delivered in form of the wakeupIntent
+            am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
+        } else {
+            // cancel alarms
+            Log.d(LOG_TAG, "cancelling future alarms on request");
+            am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
+        }
+    }
+
+    private void stopService(Intent i) {
+        Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
+        if (msgr == null) {
+            Log.e(LOG_TAG, "no stop service callback found, cannot stop");
+        } else {
+            Message msg = new Message();
+            msg.what = WakeLoopService.MSG_STOP_SERVICE;
+            try {
+                msgr.send(msg);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
+            }
+        }
+    }
+
+    private void screenOn(Context context) {
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        @SuppressWarnings("deprecation")
+        WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
+                PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
+        wl.acquire(500);
+    }
+}