Merge "Refactoring of UiAutomator to use the new UI test APIs."
diff --git a/uiautomator/api/current.txt b/uiautomator/api/current.txt
index fc11007..a8f287b 100644
--- a/uiautomator/api/current.txt
+++ b/uiautomator/api/current.txt
@@ -167,6 +167,7 @@
     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);
+    method public com.android.uiautomator.core.UiSelector viewId(java.lang.String);
   }
 
   public abstract interface UiWatcher {
@@ -181,6 +182,18 @@
     method public abstract void sendStatus(int, android.os.Bundle);
   }
 
+  public class OnDeviceUiTestCase extends android.test.UiTestCase {
+    ctor public OnDeviceUiTestCase();
+    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);
+  }
+
+  public class OnDeviceUiTestRunner extends android.test.InstrumentationTestRunner {
+    ctor public OnDeviceUiTestRunner();
+  }
+
   public class UiAutomatorTestCase extends junit.framework.TestCase {
     ctor public UiAutomatorTestCase();
     method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
diff --git a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index cceef88..6f5ac1c 100644
--- a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -16,14 +16,16 @@
 
 package com.android.commands.uiautomator;
 
-import android.accessibilityservice.UiTestAutomationBridge;
+import android.app.UiAutomation;
 import android.os.Environment;
 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
@@ -57,21 +59,27 @@
         if (args.length > 0) {
             dumpFile = new File(args[0]);
         }
-        UiTestAutomationBridge bridge = new UiTestAutomationBridge();
-        bridge.connect();
+        UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
+        automationWrapper.connect();
         // 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.
-        bridge.waitForIdle(1000, 1000 * 10);
-        AccessibilityNodeInfo info = bridge.getRootAccessibilityNodeInfoInActiveWindow();
-        if (info == null) {
-            System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
+        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;
+            }
+            AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile);
+        } catch (TimeoutException re) {
+            System.err.println("ERROR: could not get idle state.");
             return;
+        } finally {
+            automationWrapper.disconnect();
         }
-        AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile);
-        bridge.disconnect();
         System.out.println(
                 String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath()));
     }
-
 }
diff --git a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
index 79428e9..ce55f18 100644
--- a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
+++ b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
@@ -16,10 +16,11 @@
 
 package com.android.commands.uiautomator;
 
-import android.accessibilityservice.UiTestAutomationBridge;
+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;
@@ -49,15 +50,17 @@
 
     @Override
     public void run(String[] args) {
-        final UiTestAutomationBridge bridge = new UiTestAutomationBridge() {
+        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()));
             }
-        };
-        bridge.connect();
+        });
         // there's really no way to stop, essentially we just block indefinitely here and wait
         // for user to press Ctrl+C
         synchronized (mQuitLock) {
@@ -67,5 +70,6 @@
                 e.printStackTrace();
             }
         }
+        automationWrapper.disconnect();
     }
 }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
index 93a162e..e712559 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
@@ -16,22 +16,13 @@
 
 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.graphics.Point;
-import android.hardware.input.InputManager;
-import android.os.Binder;
-import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.InputDevice;
@@ -39,7 +30,6 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.Predicate;
@@ -69,8 +59,6 @@
 
     private final IWindowManager mWindowManager;
 
-    private final long mLongPressTimeout;
-
     private static final long REGULAR_CLICK_LENGTH = 100;
 
     private long mDownTime;
@@ -85,54 +73,6 @@
             throw new RuntimeException("Unable to connect to WindowManager, "
                     + "is the system running?");
         }
-
-        // the value returned is on the border of going undetected as used
-        // by this framework during long presses. Adding few extra 100ms
-        // of long press time helps ensure long enough time for a valid
-        // longClick detection.
-        mLongPressTimeout = getSystemLongPressTime() * 2 + 100;
-    }
-
-    /**
-     * Get the system long press time
-     * @return milliseconds
-     */
-    private 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;
     }
 
     /**
@@ -170,7 +110,6 @@
                 Boolean.toString(waitForAll), eventTypes);
         Log.d(LOG_TAG, logString);
 
-        mUiAutomatorBridge.setOperationTime();
         Runnable command = new Runnable() {
             @Override
             public void run() {
@@ -266,7 +205,6 @@
      */
     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
             final int eventType, long timeout) {
-        mUiAutomatorBridge.setOperationTime();
         Runnable command = new Runnable() {
             @Override
             public void run() {
@@ -295,7 +233,6 @@
      */
     public boolean click(int x, int y) {
         Log.d(LOG_TAG, "click (" + x + ", " + y + ")");
-        mUiAutomatorBridge.setOperationTime();
 
         if (touchDown(x, y)) {
             SystemClock.sleep(REGULAR_CLICK_LENGTH);
@@ -325,9 +262,8 @@
             Log.d(LOG_TAG, "longTap (" + x + ", " + y + ")");
         }
 
-        mUiAutomatorBridge.setOperationTime();
         if (touchDown(x, y)) {
-            SystemClock.sleep(mLongPressTimeout);
+            SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
             if(touchUp(x, y)) {
                 return true;
             }
@@ -510,7 +446,6 @@
             Log.d(LOG_TAG, "sendText (" + text + ")");
         }
 
-        mUiAutomatorBridge.setOperationTime();
         KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
         if (events != null) {
             for (KeyEvent event2 : events) {
@@ -534,7 +469,6 @@
             Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
         }
 
-        mUiAutomatorBridge.setOperationTime();
         final long eventTime = SystemClock.uptimeMillis();
         KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN,
                 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
@@ -557,8 +491,8 @@
      * @throws RemoteException
      */
     public boolean isNaturalRotation() throws RemoteException {
-        return mWindowManager.getRotation() == Surface.ROTATION_0
-                || mWindowManager.getRotation() == Surface.ROTATION_180;
+        return mWindowManager.getRotation() == UiAutomation.ROTATION_FREEZE_0
+                || mWindowManager.getRotation() == UiAutomation.ROTATION_FREEZE_180;
     }
 
     /**
@@ -569,8 +503,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationRight() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_270);
+    public void setRotationRight() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
     }
 
     /**
@@ -581,8 +515,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationLeft() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_90);
+    public void setRotationLeft() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
     }
 
     /**
@@ -593,8 +527,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationNatural() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_0);
+    public void setRotationNatural() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
     }
 
     /**
@@ -602,8 +536,8 @@
      * current rotation state.
      * @throws RemoteException
      */
-    public void freezeRotation() throws RemoteException {
-        mWindowManager.freezeRotation(-1);
+    public void freezeRotation() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
     }
 
     /**
@@ -611,8 +545,8 @@
      * allowing its contents to rotate with the device physical rotation.
      * @throws RemoteException
      */
-    public void unfreezeRotation() throws RemoteException {
-        mWindowManager.thawRotation();
+    public void unfreezeRotation() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
     }
 
     /**
@@ -654,8 +588,7 @@
         return pm.isScreenOn();
     }
 
-    private static boolean injectEventSync(InputEvent event) {
-        return InputManager.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+    private boolean injectEventSync(InputEvent event) {
+        return mUiAutomatorBridge.injectInputEvent(event, true);
     }
 }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java b/uiautomator/library/src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
new file mode 100644
index 0000000..a668cf5
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Service;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.view.Display;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+class OnDeviceUiAutomatorBridge extends UiAutomatorBridge {
+
+    private final Context mContext;
+
+    public OnDeviceUiAutomatorBridge(Context context, UiAutomation uiAutomation) {
+        super(uiAutomation);
+        mContext = context;
+    }
+
+    public Display getDefaultDisplay() {
+        WindowManager windowManager = (WindowManager)
+                mContext.getSystemService(Service.WINDOW_SERVICE);
+        return windowManager.getDefaultDisplay();
+    }
+
+    public long getSystemLongPressTime() {
+        return ViewConfiguration.getLongPressTimeout();
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java b/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
index 0af603a..6931528 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
@@ -15,12 +15,12 @@
  */
 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;
 
-import com.android.uiautomator.core.UiAutomatorBridge.AccessibilityEventListener;
 
 /**
  * The QueryController main purpose is to translate a {@link UiSelector} selectors to
@@ -55,7 +55,7 @@
 
     public QueryController(UiAutomatorBridge bridge) {
         mUiAutomatorBridge = bridge;
-        bridge.addAccessibilityEventListener(new AccessibilityEventListener() {
+        bridge.setOnAccessibilityEventListener(new OnAccessibilityEventListener() {
             @Override
             public void onAccessibilityEvent(AccessibilityEvent event) {
                 synchronized (mLock) {
@@ -169,7 +169,7 @@
         final long waitInterval = 250;
         AccessibilityNodeInfo rootNode = null;
         for(int x = 0; x < maxRetry; x++) {
-            rootNode = mUiAutomatorBridge.getRootAccessibilityNodeInfoInActiveWindow();
+            rootNode = mUiAutomatorBridge.getRootInActiveWindow();
             if (rootNode != null) {
                 return rootNode;
             }
@@ -480,7 +480,7 @@
     }
 
     public AccessibilityNodeInfo getAccessibilityRootNode() {
-        return mUiAutomatorBridge.getRootAccessibilityNodeInfoInActiveWindow();
+        return mUiAutomatorBridge.getRootInActiveWindow();
     }
 
     /**
diff --git a/uiautomator/library/src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/uiautomator/library/src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
new file mode 100644
index 0000000..ebf99f2
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -0,0 +1,87 @@
+/*
+ * 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.UiAutomation;
+import android.app.IActivityManager.ContentProviderHolder;
+import android.content.IContentProvider;
+import android.database.Cursor;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+
+class ShellUiAutomatorBridge extends UiAutomatorBridge {
+
+    private static final String LOG_TAG = ShellUiAutomatorBridge.class.getSimpleName();
+
+    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;
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/uiautomator/library/src/com/android/uiautomator/core/UiAutomationShellWrapper.java
new file mode 100644
index 0000000..eb43cb5
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -0,0 +1,36 @@
+package com.android.uiautomator.core;
+
+import android.app.UiAutomation;
+import android.app.UiAutomationConnection;
+import android.os.HandlerThread;
+
+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();
+    }
+
+    public void disconnect() {
+        if (!mHandlerThread.isAlive()) {
+            throw new IllegalStateException("Already disconnected!");
+        }
+        mUiAutomation.disconnect();
+        mHandlerThread.quit();
+    }
+
+    public UiAutomation getUiAutomation() {
+        return mUiAutomation;
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java b/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java
index 90aa4df..0387a62 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java
@@ -1,82 +1,50 @@
-/*
- * 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.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 com.android.internal.util.Predicate;
 
-import android.accessibilityservice.UiTestAutomationBridge;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 
-class UiAutomatorBridge extends UiTestAutomationBridge {
+abstract class UiAutomatorBridge {
 
-    private static final String LOGTAG = UiAutomatorBridge.class.getSimpleName();
+    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.
+   /**
+    * 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 value is used to wait for the UI to go busy after an action. This has little
-    // bearing on the appearance of test execution speeds. This value is used as a maximum
-    // time to wait for busy state where it is possible to occur much sooner.
-    private static final long WAIT_TIME_FROM_IDLE_TO_BUSY_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...
+   /**
+    * 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
 
-    // Poll time used to check for last accessibility event time
-    private static final long BUSY_STATE_POLL_TIME = 50; //ms
-
-    private final CopyOnWriteArrayList<AccessibilityEventListener> mListeners =
-            new CopyOnWriteArrayList<AccessibilityEventListener>();
-
-    private final Object mLock = new Object();
+    private final UiAutomation mUiAutomation;
 
     private final InteractionController mInteractionController;
 
     private final QueryController mQueryController;
 
-    private long mLastEventTime = 0;
-    private long mLastOperationTime = 0;
-
-    private volatile boolean mWaitingForEventDelivery;
-
-    public static final long TIMEOUT_ASYNC_PROCESSING = 5000;
-
-    private final LinkedBlockingQueue<AccessibilityEvent> mEventQueue =
-        new LinkedBlockingQueue<AccessibilityEvent>(10);
-
-    public interface AccessibilityEventListener {
-        public void onAccessibilityEvent(AccessibilityEvent event);
-    }
-
-    UiAutomatorBridge() {
+    UiAutomatorBridge(UiAutomation uiAutomation) {
+        mUiAutomation = uiAutomation;
         mInteractionController = new InteractionController(this);
         mQueryController = new QueryController(this);
-        connect();
     }
 
     InteractionController getInteractionController() {
@@ -87,47 +55,28 @@
         return mQueryController;
     }
 
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        super.onAccessibilityEvent(event);
-        Log.d(LOGTAG, event.toString());
-        if (mWaitingForEventDelivery) {
-            try {
-                AccessibilityEvent clone = AccessibilityEvent.obtain(event);
-                mEventQueue.offer(clone, TIMEOUT_ASYNC_PROCESSING, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ie) {
-                /* ignore */
-            }
-            if (!mWaitingForEventDelivery) {
-                mEventQueue.clear();
-            }
-        }
-        mLastEventTime = SystemClock.uptimeMillis();
-        notifyListeners(event);
+    public void connect() {
+        mUiAutomation.connect();
     }
 
-
-    void addAccessibilityEventListener(AccessibilityEventListener listener) {
-        mListeners.add(listener);
+    public void disconnect() {
+        mUiAutomation.disconnect();
     }
 
-    private void notifyListeners(AccessibilityEvent event) {
-        for (AccessibilityEventListener listener : mListeners) {
-            listener.onAccessibilityEvent(event);
-        }
+    public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
+        mUiAutomation.setOnAccessibilityEventListener(listener);
     }
 
-    @Override
-    public void waitForIdle(long idleTimeout, long globalTimeout) {
-        long start = SystemClock.uptimeMillis();
-        while ((SystemClock.uptimeMillis() - start) < WAIT_TIME_FROM_IDLE_TO_BUSY_STATE) {
-            if (getLastOperationTime() > getLastEventTime()) {
-                SystemClock.sleep(BUSY_STATE_POLL_TIME);
-            } else {
-                break;
-            }
-        }
-        super.waitForIdle(idleTimeout, globalTimeout);
+    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 waitForIdle() {
@@ -135,67 +84,48 @@
     }
 
     public void waitForIdle(long timeout) {
-        waitForIdle(QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE, timeout);
-    }
-
-    private long getLastEventTime() {
-        synchronized (mLock) {
-            return mLastEventTime;
-        }
-    }
-
-    private long getLastOperationTime() {
-        synchronized (mLock) {
-            return mLastOperationTime;
-        }
-    }
-
-    void setOperationTime() {
-        synchronized (mLock) {
-            mLastOperationTime = SystemClock.uptimeMillis();
-        }
-    }
-
-    void updateEventTime() {
-        synchronized (mLock) {
-            mLastEventTime = SystemClock.uptimeMillis();
+        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,
-            Predicate<AccessibilityEvent> predicate, long timeoutMillis)
-            throws TimeoutException, Exception {
-        // Prepare to wait for an event.
-        mWaitingForEventDelivery = true;
-        // Execute the command.
-        command.run();
-        // Wait for the event.
-        final long startTimeMillis = SystemClock.uptimeMillis();
-        while (true) {
-            // Check if timed out and if not wait.
-            final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
-            final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
-            if (remainingTimeMillis <= 0) {
-                mWaitingForEventDelivery = false;
-                mEventQueue.clear();
-                throw new TimeoutException("Expected event not received within: "
-                        + timeoutMillis + " ms.");
+            Predicate<AccessibilityEvent> 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();
             }
-            AccessibilityEvent event = null;
-            try {
-                event = mEventQueue.poll(remainingTimeMillis, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ie) {
-                /* ignore */
-            }
-            if (event != null) {
-                if (predicate.apply(event)) {
-                    mWaitingForEventDelivery = false;
-                    mEventQueue.clear();
-                    return event;
-                } else {
-                    event.recycle();
+        } 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 abstract Display getDefaultDisplay();
+
+    public abstract long getSystemLongPressTime();
 }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java b/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
index b74aec1..b468574 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
@@ -16,6 +16,8 @@
 
 package com.android.uiautomator.core;
 
+import android.app.UiAutomation;
+import android.app.UiAutomationConnection;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -69,27 +71,38 @@
     private boolean mInWatcherContext = false;
 
     // provides access the {@link QueryController} and {@link InteractionController}
-    private final UiAutomatorBridge mUiAutomationBridge;
+    private UiAutomatorBridge mUiAutomationBridge;
 
     // reference to self
-    private static UiDevice mDevice;
+    private static UiDevice sDevice;
 
     private UiDevice() {
-        mUiAutomationBridge = new UiAutomatorBridge();
-        mDevice = this;
+        /* hide constructor */
     }
 
+    /**
+     * @hide
+     */
+    public void initialize(Context context, UiAutomation uiAutomation) {
+        if (context == null) {
+            mUiAutomationBridge = new ShellUiAutomatorBridge(uiAutomation);
+        } else {
+            mUiAutomationBridge = new OnDeviceUiAutomatorBridge(context, uiAutomation);
+        }
+    }
+    
     boolean isInWatcherContext() {
         return mInWatcherContext;
     }
 
     /**
      * Provides access the {@link QueryController} and {@link InteractionController}
-     * @return {@link UiAutomatorBridge}
+     * @return {@link ShellUiAutomatorBridge}
      */
     UiAutomatorBridge getAutomatorBridge() {
         return mUiAutomationBridge;
     }
+
     /**
      * Retrieves a singleton instance of UiDevice
      *
@@ -97,10 +110,10 @@
      * @since API Level 16
      */
     public static UiDevice getInstance() {
-        if (mDevice == null) {
-            mDevice = new UiDevice();
+        if (sDevice == null) {
+            sDevice = new UiDevice();
         }
-        return mDevice;
+        return sDevice;
     }
 
     /**
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java b/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
index 8963c38..cb4e9ad 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
@@ -58,6 +58,7 @@
     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_VIEW_ID = 29;
 
     private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>();
 
@@ -279,6 +280,17 @@
     }
 
     /**
+     * Set the search criteria to match the given view id.
+     *
+     * @param desc Value to match
+     * @return UiSelector with the specified search criteria
+     * @since API Level 18
+     */
+    public UiSelector viewId(String viewId) {
+        return buildSelector(SELECTOR_VIEW_ID, viewId);
+    }
+
+    /**
      * Set the search criteria to match the widget by its node
      * index in the layout hierarchy.
      *
@@ -734,6 +746,11 @@
                     return false;
                 }
                 break;
+            case UiSelector.SELECTOR_VIEW_ID:
+                if (node.getViewId() != getString(criterion)) {
+                    return false;
+                }
+                break;
             }
         }
         return matchOrUpdateInstance();
@@ -941,6 +958,9 @@
             case SELECTOR_PACKAGE_NAME_REGEX:
                 builder.append("PACKAGE_NAME_REGEX=").append(mSelectorAttributes.valueAt(i));
                 break;
+            case SELECTOR_VIEW_ID:
+                builder.append("VIEW_ID=").append(mSelectorAttributes.valueAt(i));
+                break;
             default:
                 builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i));
             }
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java b/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
new file mode 100644
index 0000000..dc38d25
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
@@ -0,0 +1,141 @@
+/*
+ * 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.UiAutomation;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.UiDevice;
+
+import java.util.List;
+
+/**
+ * UI automator test case that is executed on the device. 
+ */
+public class OnDeviceUiTestCase extends AndroidTestCase {
+    private static final String DISABLE_IME = "disable_ime";
+    private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
+    private boolean mShouldDisableIme;
+
+    private UiAutomation mUiAutomation;
+    private Bundle mParams;
+
+    private final IAutomationSupport mAutomationSupport = new IAutomationSupport() {
+        @Override
+        public void sendStatus(int resultCode, Bundle status) {
+            sendStatus(resultCode, status);
+        }
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        UiDevice.getInstance().initialize(getContext(), mUiAutomation);
+        mShouldDisableIme = "true".equals(getParams().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 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;
+    }
+
+    /**
+     * Initializes this test case.
+     *
+     * @param uiAutomation An {@link UiAutomation} instance.
+     * @param params Instrumentation arguments.
+     */
+    void initialize(UiAutomation uiAutomation,  Bundle params) {
+        mUiAutomation = uiAutomation;
+        mParams = params;
+    }
+
+    /**
+     * Provides support for running tests to report interim status
+     *
+     * @return IAutomationSupport
+     * @since API Level 16
+     */
+    public IAutomationSupport getAutomationSupport() {
+        return mAutomationSupport;
+    }
+
+    /**
+     * 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/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java b/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
new file mode 100644
index 0000000..857d7e9
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.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.uiautomator.testrunner;
+
+import android.test.AndroidTestRunner;
+import android.test.InstrumentationTestRunner;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import java.util.List;
+
+/**
+ * Test runner for {@link OnDeviceUiTestCase}s. Such tests are executed
+ * on the device and have access to an applications context.
+ */
+public class OnDeviceUiTestRunner extends InstrumentationTestRunner {
+
+    @Override
+    protected AndroidTestRunner getAndroidTestRunner() {
+        return new AndroidTestRunner() {
+            @Override
+            public void runTest(TestResult testResult) {
+                List<TestCase> testCases = getTestCases();
+                final int testCaseCount = testCases.size();
+                for (int i = 0; i < testCaseCount; i++) {
+                    TestCase testCase = testCases.get(i);
+                    if (testCase instanceof OnDeviceUiTestCase) {
+                        OnDeviceUiTestCase uiTestCase = (OnDeviceUiTestCase) testCase;
+                        uiTestCase.initialize(OnDeviceUiTestRunner.this.getUiAutomation(),
+                                getArguments());
+                    }
+                }
+                super.runTest(testResult);
+            }
+        };
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
index 4f41a5c..4405927 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -22,12 +22,14 @@
 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.Tracer;
+import com.android.uiautomator.core.UiAutomationShellWrapper;
 import com.android.uiautomator.core.UiDevice;
 
 import junit.framework.AssertionFailedError;
@@ -54,6 +56,8 @@
     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 Bundle mParams = null;
     private UiDevice mUiDevice;
@@ -67,6 +71,8 @@
     };
     private List<TestListener> mTestListeners = new ArrayList<TestListener>();
 
+    private HandlerThread mHandlerThread;
+
     public void run(List<String> testClasses, Bundle params, boolean debug) {
         Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
             @Override
@@ -102,6 +108,12 @@
         if (mDebug) {
             Debug.waitForDebugger();
         }
+        mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
+        mHandlerThread.setDaemon(true);
+        mHandlerThread.start();
+        UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
+        automationWrapper.connect();
+        UiDevice.getInstance().initialize(null, automationWrapper.getUiAutomation());
         mUiDevice = UiDevice.getInstance();
         List<TestCase> testCases = collector.getTestCases();
         Bundle testRunOutput = new Bundle();
@@ -149,6 +161,8 @@
         } finally {
             long runTime = SystemClock.uptimeMillis() - startTime;
             resultPrinter.print(testRunResult, runTime, testRunOutput);
+            automationWrapper.disconnect();
+            mHandlerThread.quit();
         }
     }