am bda2992d: am ff3ae2fb: am 0aac9989: Move the uiautomator test libraries to frameworks/testing to solve the unbundle configure issue.

# Via Android Git Automerger (2) and Xia Wang (1)
* commit 'bda2992d209fb8607eaa5132211470b714428ec8':
  Move the uiautomator test libraries to frameworks/testing to solve the unbundle configure issue.
diff --git a/.gitignore b/.gitignore
index 3a5f35d..aaf6b34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
 *.class
 bin/
 *.properties
-*.project
 *.cproject
 *.settings
 *.pyc
diff --git a/androidtestlib/src/com/android/test/InjectBundle.java b/androidtestlib/src/com/android/test/InjectBundle.java
new file mode 100644
index 0000000..f3c2399
--- /dev/null
+++ b/androidtestlib/src/com/android/test/InjectBundle.java
@@ -0,0 +1,46 @@
+/*
+ * 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.test;
+
+import android.os.Bundle;
+
+import junit.framework.Test;
+
+import org.junit.Before;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this to inject a {@link Bundle} containing the command line arguments passed to the
+ * test runner into your JUnit4-style test.
+ * <p/>
+ * To use, just add the correct annotation to an {@link Bundle} field like this:
+ * <pre>
+ *     &#64;InjectBundle public Bundle mMyBundle;
+ * </pre>
+ * The test runner will set the value of this field with the {@link Bundle} after
+ * object construction but before any {@link Before} methods are called.
+ * <p/>
+ * Declaring this in a JUnit3 test (ie a class that is a {@link Test}) will have no effect.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface InjectBundle {
+
+}
diff --git a/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java b/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
index a932523..d3bf6bd 100644
--- a/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
+++ b/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
@@ -305,7 +305,7 @@
         if (getBooleanArgument(ARGUMENT_LOG_ONLY)) {
             builder.setSkipExecution(true);
         }
-        return builder.build(this);
+        return builder.build(this, arguments);
     }
 
     /**
diff --git a/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java b/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
index d56861a..328d413 100644
--- a/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
+++ b/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 
 import com.android.test.runner.junit3.AndroidJUnit3Builder;
 import com.android.test.runner.junit4.AndroidJUnit4Builder;
@@ -29,16 +30,14 @@
  */
 class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
 
-    private final Instrumentation mInstr;
     private final AndroidJUnit3Builder mAndroidJUnit3Builder;
     private final AndroidJUnit4Builder mAndroidJUnit4Builder;
 
-    public AndroidRunnerBuilder(boolean canUseSuiteMethod, Instrumentation instr,
+    public AndroidRunnerBuilder(boolean canUseSuiteMethod, Instrumentation instr, Bundle bundle,
             boolean skipExecution) {
         super(canUseSuiteMethod);
-        mInstr = instr;
-        mAndroidJUnit3Builder = new AndroidJUnit3Builder(mInstr, skipExecution);
-        mAndroidJUnit4Builder = new AndroidJUnit4Builder(mInstr, skipExecution);
+        mAndroidJUnit3Builder = new AndroidJUnit3Builder(instr, skipExecution);
+        mAndroidJUnit4Builder = new AndroidJUnit4Builder(instr, bundle, skipExecution);
     }
 
     @Override
diff --git a/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java b/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
index a15b70e..7077517 100644
--- a/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
+++ b/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -207,13 +208,13 @@
      * If no classes have been explicitly added, will scan the classpath for all tests.
      *
      */
-    public TestRequest build(Instrumentation instr) {
+    public TestRequest build(Instrumentation instr, Bundle bundle) {
         if (mTestLoader.isEmpty()) {
             // no class restrictions have been specified. Load all classes
             loadClassesFromClassPath();
         }
 
-        Request request = classes(instr, mSkipExecution, new Computer(),
+        Request request = classes(instr, bundle, mSkipExecution, new Computer(),
                 mTestLoader.getLoadedClasses().toArray(new Class[0]));
         return new TestRequest(mTestLoader.getLoadFailures(), request.filterWith(mFilter));
     }
@@ -223,14 +224,17 @@
      * in a set of classes.
      *
      * @param instr the {@link Instrumentation} to inject into any tests that require it
+     * @param bundle the {@link Bundle} of command line args to inject into any tests that require
+     *         it
      * @param computer Helps construct Runners from classes
      * @param classes the classes containing the tests
      * @return a <code>Request</code> that will cause all tests in the classes to be run
      */
-    private static Request classes(Instrumentation instr, boolean skipExecution,
+    private static Request classes(Instrumentation instr, Bundle bundle, boolean skipExecution,
             Computer computer, Class<?>... classes) {
         try {
-            AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, skipExecution);
+            AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, bundle,
+                    skipExecution);
             Runner suite = computer.getSuite(builder, classes);
             return Request.runner(suite);
         } catch (InitializationError e) {
diff --git a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
index 1b14a9a..6a08b6a 100644
--- a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
+++ b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner.junit4;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 
 import com.android.test.InjectContext;
 import com.android.test.InjectInstrumentation;
@@ -32,10 +33,12 @@
 public class AndroidJUnit4Builder extends RunnerBuilder {
 
     private final Instrumentation mInstrumentation;
+    private final Bundle mBundle;
     private boolean mSkipExecution;
 
-    public AndroidJUnit4Builder(Instrumentation instr, boolean skipExecution) {
+    public AndroidJUnit4Builder(Instrumentation instr, Bundle bundle, boolean skipExecution) {
         mInstrumentation = instr;
+        mBundle = bundle;
         mSkipExecution = skipExecution;
     }
 
@@ -45,7 +48,7 @@
             return new NonExecutingJUnit4ClassRunner(testClass);
         }
         if (hasInjectedFields(testClass)) {
-            return new AndroidJUnit4ClassRunner(testClass, mInstrumentation);
+            return new AndroidJUnit4ClassRunner(testClass, mInstrumentation, mBundle);
         }
         return null;
     }
diff --git a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
index 41d5ad4..4b6e00c 100644
--- a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
+++ b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
@@ -17,8 +17,10 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Bundle;
 import android.util.Log;
 
+import com.android.test.InjectBundle;
 import com.android.test.InjectContext;
 import com.android.test.InjectInstrumentation;
 
@@ -37,6 +39,7 @@
 
     private static final String LOG_TAG = "AndroidJUnit4ClassRunner";
     private final Instrumentation mInstr;
+    private final Bundle mBundle;
 
     @SuppressWarnings("serial")
     private static class InvalidInjectException extends Exception {
@@ -45,10 +48,11 @@
         }
     }
 
-    public AndroidJUnit4ClassRunner(Class<?> klass, Instrumentation instr)
+    public AndroidJUnit4ClassRunner(Class<?> klass, Instrumentation instr, Bundle bundle)
             throws InitializationError {
         super(klass);
         mInstr = instr;
+        mBundle = bundle;
     }
 
     @Override
@@ -76,6 +80,11 @@
         for (FrameworkField contextField : contextFields) {
             validateInjectField(errors, contextField, Context.class);
         }
+        List<FrameworkField> bundleFields = getTestClass().getAnnotatedFields(
+                InjectBundle.class);
+        for (FrameworkField bundleField : bundleFields) {
+            validateInjectField(errors, bundleField, Context.class);
+        }
     }
 
     private void validateInjectField(List<Throwable> errors, FrameworkField instrField,
@@ -104,6 +113,11 @@
         for (FrameworkField contextField : contextFields) {
             setFieldValue(test, contextField.getField(), mInstr.getTargetContext());
         }
+        List<FrameworkField> bundleFields = getTestClass().getAnnotatedFields(
+                InjectBundle.class);
+        for (FrameworkField bundleField : bundleFields) {
+            setFieldValue(test, bundleField.getField(), mBundle);
+        }
     }
 
     private void setFieldValue(Object test, Field field, Object value) {
diff --git a/androidtestlib/tests/.classpath b/androidtestlib/tests/.classpath
new file mode 100644
index 0000000..99b9c55
--- /dev/null
+++ b/androidtestlib/tests/.classpath
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="src" path="dexmaker-java-src"/>
+	<classpathentry kind="src" path="dexmaker-dx-src"/>
+	<classpathentry kind="src" path="hamcrest-src"/>
+	<classpathentry kind="src" path="junit4-src"/>
+	<classpathentry kind="src" path="android-test-lib-src"/>
+	<classpathentry kind="src" path="objenesis-src"/>
+	<classpathentry kind="src" path="mockito-src"/>
+	<classpathentry kind="src" path="dexmaker-mockito"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/androidtestlib/tests/.gitignore b/androidtestlib/tests/.gitignore
index 4f50fa0..4f62b84 100644
--- a/androidtestlib/tests/.gitignore
+++ b/androidtestlib/tests/.gitignore
@@ -1,2 +1 @@
-.classpath
 gen
diff --git a/androidtestlib/tests/.project b/androidtestlib/tests/.project
new file mode 100644
index 0000000..e4e4c5e
--- /dev/null
+++ b/androidtestlib/tests/.project
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>AndroidTestLibTests</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+	<linkedResources>
+		<link>
+			<name>android-test-lib-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/frameworks/testing/androidtestlib/src</locationURI>
+		</link>
+		<link>
+			<name>dexmaker-dx-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/dexmaker/src/dx/java</locationURI>
+		</link>
+		<link>
+			<name>dexmaker-java-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/dexmaker/src/main/java</locationURI>
+		</link>
+		<link>
+			<name>dexmaker-mockito</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/dexmaker/src/mockito/java</locationURI>
+		</link>
+		<link>
+			<name>hamcrest-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/hamcrest/src</locationURI>
+		</link>
+		<link>
+			<name>junit4-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/junit/src</locationURI>
+		</link>
+		<link>
+			<name>littlemock-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/littlemock/src</locationURI>
+		</link>
+		<link>
+			<name>mockito-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/mockito/src</locationURI>
+		</link>
+		<link>
+			<name>objenesis-src</name>
+			<type>2</type>
+			<locationURI>ANDROID_TOP/external/objenesis/main/src</locationURI>
+		</link>
+	</linkedResources>
+</projectDescription>
diff --git a/androidtestlib/tests/Android.mk b/androidtestlib/tests/Android.mk
index 3d27857..fa27121 100644
--- a/androidtestlib/tests/Android.mk
+++ b/androidtestlib/tests/Android.mk
@@ -25,9 +25,10 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_SDK_VERSION := 8
+# SDK 10 needed for mockito/objnesis. Otherwise 8 would work
+LOCAL_SDK_VERSION := 10
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-test-lib dexmaker littlemock
+LOCAL_STATIC_JAVA_LIBRARIES := android-test-lib mockito-target dexmaker
 
 LOCAL_PROGUARD_ENABLED := disabled
 
diff --git a/androidtestlib/tests/src/com/android/test/runner/AndroidJUnitRunnerTest.java b/androidtestlib/tests/src/com/android/test/runner/AndroidJUnitRunnerTest.java
index 6a24a47..441c4f0 100644
--- a/androidtestlib/tests/src/com/android/test/runner/AndroidJUnitRunnerTest.java
+++ b/androidtestlib/tests/src/com/android/test/runner/AndroidJUnitRunnerTest.java
@@ -18,11 +18,12 @@
 import android.content.Context;
 import android.os.Bundle;
 
-import com.google.testing.littlemock.LittleMock;
-import com.google.testing.littlemock.Mock;
-
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
@@ -57,7 +58,7 @@
         };
         mAndroidJUnitRunner.setArguments(new Bundle());
         mStubStream = new PrintStream(new ByteArrayOutputStream());
-        LittleMock.initMocks(this);
+        MockitoAnnotations.initMocks(this);
     }
 
     /**
@@ -68,9 +69,8 @@
     public void testBuildRequest_singleClass() {
         Bundle b = new Bundle();
         b.putString(AndroidJUnitRunner.ARGUMENT_TEST_CLASS, "ClassName");
-        LittleMock.doNothing().when(mMockBuilder).addTestClass("ClassName");
         mAndroidJUnitRunner.buildRequest(b, mStubStream);
-        LittleMock.verify(mMockBuilder);
+        Mockito.verify(mMockBuilder).addTestClass("ClassName");
     }
 
     /**
@@ -81,10 +81,9 @@
     public void testBuildRequest_multiClass() {
         Bundle b = new Bundle();
         b.putString(AndroidJUnitRunner.ARGUMENT_TEST_CLASS, "ClassName1,ClassName2");
-        LittleMock.doNothing().when(mMockBuilder).addTestClass("ClassName1");
-        LittleMock.doNothing().when(mMockBuilder).addTestClass("ClassName2");
         mAndroidJUnitRunner.buildRequest(b, mStubStream);
-        LittleMock.verify(mMockBuilder);
+        Mockito.verify(mMockBuilder).addTestClass("ClassName1");
+        Mockito.verify(mMockBuilder).addTestClass("ClassName2");
     }
 
     /**
@@ -95,8 +94,7 @@
     public void testBuildRequest_method() {
         Bundle b = new Bundle();
         b.putString(AndroidJUnitRunner.ARGUMENT_TEST_CLASS, "ClassName1#method");
-        LittleMock.doNothing().when(mMockBuilder).addTestMethod("ClassName1", "method");
         mAndroidJUnitRunner.buildRequest(b, mStubStream);
-        LittleMock.verify(mMockBuilder);
+        Mockito.verify(mMockBuilder).addTestMethod("ClassName1", "method");
     }
 }
diff --git a/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java b/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
index 4d7d9bb..9c4ae12 100644
--- a/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
+++ b/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
@@ -16,8 +16,10 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.test.InjectBundle;
 import com.android.test.InjectInstrumentation;
 
 import org.junit.Assert;
@@ -60,6 +62,10 @@
     @InjectInstrumentation
     public Instrumentation mInstr;
 
+    @InjectBundle
+    public Bundle mBundle;
+
+
     /**
      * Test initial condition for size filtering - that all tests run when no filter is attached
      */
@@ -67,7 +73,7 @@
     public void testNoSize() {
         TestRequestBuilder b = new TestRequestBuilder(new PrintStream(new ByteArrayOutputStream()));
         b.addTestClass(SampleTest.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(2, result.getRunCount());
@@ -81,7 +87,7 @@
         TestRequestBuilder b = new TestRequestBuilder(new PrintStream(new ByteArrayOutputStream()));
         b.addTestClass(SampleTest.class.getName());
         b.addTestSizeFilter("small");
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(1, result.getRunCount());
@@ -96,7 +102,7 @@
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
         b.addTestSizeFilter("small");
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(3, result.getRunCount());
@@ -111,7 +117,7 @@
         b.addAnnotationInclusionFilter(SmallTest.class.getName());
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(3, result.getRunCount());
@@ -126,7 +132,7 @@
         b.addAnnotationExclusionFilter(SmallTest.class.getName());
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(1, result.getRunCount());
diff --git a/app-tests/Android.mk b/app-tests/Android.mk
new file mode 100644
index 0000000..c141484
--- /dev/null
+++ b/app-tests/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/app-tests/AppLaunchTest/Android.mk b/app-tests/AppLaunchTest/Android.mk
new file mode 100644
index 0000000..5e874d0
--- /dev/null
+++ b/app-tests/AppLaunchTest/Android.mk
@@ -0,0 +1,31 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AppLaunchTest
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := 8
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/app-tests/AppLaunchTest/AndroidManifest.xml b/app-tests/AppLaunchTest/AndroidManifest.xml
new file mode 100644
index 0000000..ef5f1ea
--- /dev/null
+++ b/app-tests/AppLaunchTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.applaunchtest" >
+
+    <instrumentation
+        android:name="com.android.applaunchtest.AppLaunchRunner"
+        android:targetPackage="com.android.applaunchtest" />
+
+    <application
+        android:label="@string/app_name" android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/app-tests/AppLaunchTest/res/values/strings.xml b/app-tests/AppLaunchTest/res/values/strings.xml
new file mode 100644
index 0000000..f8face5
--- /dev/null
+++ b/app-tests/AppLaunchTest/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<resources>
+
+    <string name="app_name">AppLaunchTest</string>
+
+</resources>
diff --git a/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchRunner.java b/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchRunner.java
new file mode 100644
index 0000000..08d0894
--- /dev/null
+++ b/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchRunner.java
@@ -0,0 +1,57 @@
+/*
+ * 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.applaunchtest;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+
+/**
+ * A special test runner that accepts arguments needed to run {@link AppLaunchTest}.
+ */
+public class AppLaunchRunner extends InstrumentationTestRunner {
+
+    private String mAppPackageName;
+    private long mWaitTime;
+
+    @Override
+    public void onCreate(Bundle args) {
+        mAppPackageName = args.getString("packageName");
+        String waitTimeString = args.getString("appLaunchWait");
+        if (waitTimeString != null) {
+            mWaitTime = Long.parseLong(waitTimeString);
+        } else {
+            // default to 7 seconds
+            mWaitTime = 7000;
+        }
+        super.onCreate(args);
+    }
+
+    /**
+     * Gets the Android application package name to launch. Application must have a launchable
+     * activity that handles MAIN intent.
+     */
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Gets the number of ms to monitor for app crashes after attempting to launch the app.
+     */
+    public long getAppWaitTime() {
+        return mWaitTime;
+    }
+}
diff --git a/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchTest.java b/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchTest.java
new file mode 100644
index 0000000..257babc
--- /dev/null
+++ b/app-tests/AppLaunchTest/src/com/android/applaunchtest/AppLaunchTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.applaunchtest;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessErrorStateInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Simple tests that launches a specified app, and waits for a configurable amount of time for
+ * crashes and ANRs.
+ * <p/>
+ * If no crashes occur, test is considered passed.
+ * <p/>
+ * Derived from frameworks/base/tests/SmokeTests/... . TODO: consider refactoring to share code
+ */
+public class AppLaunchTest extends InstrumentationTestCase {
+
+    private static final String TAG = "AppLaunchTest";
+
+    private ActivityManager mActivityManager;
+    private PackageManager mPackageManager;
+    private String mPackageName;
+    private Context mContext;
+    private long mWaitTime;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+        assertNotNull("failed to get context", mContext);
+
+        mActivityManager = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mPackageManager = mContext.getPackageManager();
+        assertNotNull("failed to get activity manager", mActivityManager);
+        assertNotNull("failed to get package manager", mPackageManager);
+
+        assertTrue("Unexpected runner: AppLaunchRunner must be used",
+                getInstrumentation() instanceof AppLaunchRunner);
+        AppLaunchRunner runner = (AppLaunchRunner)getInstrumentation();
+        mPackageName = runner.getAppPackageName();
+        mWaitTime  = runner.getAppWaitTime();
+        assertNotNull("package name to launch was not provided", mPackageName);
+        assertNotNull("time to wait for app launch was not provided", mWaitTime);
+    }
+
+    /**
+     * A test that runs Launcher-launchable activity for given package name and verifies that no
+     * ANRs or crashes happened while doing so.
+     */
+    public void testLaunchActivity() throws Exception {
+        final Set<ProcessError> errSet = new LinkedHashSet<ProcessError>();
+
+        ResolveInfo app = getLauncherActivity(mPackageName, mPackageManager);
+        assertNotNull(String.format("Could not find launchable activity for %s", mPackageName),
+                app);
+        final Collection<ProcessError> errProcs = runOneActivity(app, mWaitTime);
+        if (errProcs != null) {
+            errSet.addAll(errProcs);
+         }
+
+        if (!errSet.isEmpty()) {
+            fail(String.format("Detected %d errors on launch of app %s:\n%s", errSet.size(),
+                    mPackageName, reportWrappedListContents(errSet)));
+        }
+    }
+
+    /**
+     * A method to run the specified Activity and return a {@link Collection} of the Activities that
+     * were in an error state, as listed by {@link ActivityManager.getProcessesInErrorState()}.
+     * <p />
+     * The method will launch the app, wait for waitTime seconds, check for apps in the error state
+     * and then return.
+     */
+    public Collection<ProcessError> runOneActivity(ResolveInfo app, long appLaunchWait) {
+
+        Log.i(TAG, String.format("Running activity %s/%s", app.activityInfo.packageName,
+                app.activityInfo.name));
+
+        // We check for any Crash or ANR dialogs that are already up, and we ignore them.  This is
+        // so that we don't report crashes that were caused by prior apps.
+        final Collection<ProcessError> preErrProcs =
+                ProcessError.fromCollection(mActivityManager.getProcessesInErrorState());
+
+        // launch app, and waitfor it to start/settle
+        final Intent intent = intentForActivity(app);
+        mContext.startActivity(intent);
+        try {
+            Thread.sleep(appLaunchWait);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+
+        // TODO: inject event to see if app is responding. The smoke tests press 'Home', but
+        // we don't want to do that here because we want to take screenshot on app launch
+
+        // See if there are any errors.  We wait until down here to give ANRs as much time as
+        // possible to occur.
+        final Collection<ProcessError> errProcs =
+                ProcessError.fromCollection(mActivityManager.getProcessesInErrorState());
+
+        // Distinguish the asynchronous crashes/ANRs from the synchronous ones by checking the
+        // crash package name against the package name for {@code app}
+        if (errProcs != null) {
+            Iterator<ProcessError> errIter = errProcs.iterator();
+            while (errIter.hasNext()) {
+                ProcessError err = errIter.next();
+                if (!packageMatches(app, err)) {
+                    // crash in another package. Just log it for now
+                    Log.w(TAG, String.format("Detected crash in %s when launching %s",
+                            err.info.processName, app.activityInfo.packageName));
+                    errIter.remove();
+                }
+            }
+        }
+        // Take the difference between the remaining current error processes and the ones that were
+        // present when we started.  The result is guaranteed to be:
+        // 1) Errors that are pertinent to this app's package
+        // 2) Errors that are pertinent to this particular app invocation
+        if (errProcs != null && preErrProcs != null) {
+            errProcs.removeAll(preErrProcs);
+        }
+
+        return errProcs;
+    }
+
+    /**
+     * A helper function that checks whether the specified error could have been caused by the
+     * specified app.
+     *
+     * @param app The app to check against
+     * @param err The error that we're considering
+     */
+    private static boolean packageMatches(ResolveInfo app, ProcessError err) {
+        final String appPkg = app.activityInfo.packageName;
+        final String errPkg = err.info.processName;
+        Log.d(TAG, String.format("packageMatches(%s, %s)", appPkg, errPkg));
+        return appPkg.equals(errPkg);
+    }
+
+    /**
+     * A helper function to get the launchable activity for the given package name.
+     */
+    static ResolveInfo getLauncherActivity(String packageName, PackageManager pm) {
+        final Intent launchable = new Intent(Intent.ACTION_MAIN);
+        launchable.addCategory(Intent.CATEGORY_LAUNCHER);
+        launchable.setPackage(packageName);
+        return pm.resolveActivity(launchable, 0);
+    }
+
+    /**
+     * A helper function to create an {@link Intent} to run, given a {@link ResolveInfo} specifying
+     * an activity to be launched.
+     */
+    static Intent intentForActivity(ResolveInfo app) {
+        final ComponentName component = new ComponentName(app.activityInfo.packageName,
+                app.activityInfo.name);
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(component);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        return intent;
+    }
+
+    /**
+     * Report error reports for {@link ProcessErrorStateInfo} instances that are wrapped inside of
+     * {@link ProcessError} instances.  Just unwraps and calls
+     * {@see reportListContents(Collection<ProcessErrorStateInfo>)}.
+     */
+    static String reportWrappedListContents(Collection<ProcessError> errList) {
+        List<ProcessErrorStateInfo> newList = new ArrayList<ProcessErrorStateInfo>(errList.size());
+        for (ProcessError err : errList) {
+            newList.add(err.info);
+        }
+        return reportListContents(newList);
+    }
+
+    /**
+     * This helper function will dump the actual error reports.
+     *
+     * @param errList The error report containing one or more error records.
+     * @return Returns a string containing all of the errors.
+     */
+    private static String reportListContents(Collection<ProcessErrorStateInfo> errList) {
+        if (errList == null) return null;
+
+        StringBuilder builder = new StringBuilder();
+
+        Iterator<ProcessErrorStateInfo> iter = errList.iterator();
+        while (iter.hasNext()) {
+            ProcessErrorStateInfo entry = iter.next();
+
+            String condition;
+            switch (entry.condition) {
+            case ActivityManager.ProcessErrorStateInfo.CRASHED:
+                condition = "a CRASH";
+                break;
+            case ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING:
+                condition = "an ANR";
+                break;
+            default:
+                condition = "an unknown error";
+                break;
+            }
+
+            builder.append(String.format("Process %s encountered %s (%s)", entry.processName,
+                    condition, entry.shortMsg));
+            if (entry.condition == ActivityManager.ProcessErrorStateInfo.CRASHED) {
+                builder.append(String.format(" with stack trace:\n%s\n", entry.stackTrace));
+            }
+            builder.append("\n");
+        }
+        return builder.toString();
+    }
+
+    /**
+     * A {@link ProcessErrorStateInfo} wrapper class that hashes how we want (so that equivalent
+     * crashes are considered equal).
+     */
+    static class ProcessError {
+        public final ProcessErrorStateInfo info;
+
+        public ProcessError(ProcessErrorStateInfo newInfo) {
+            info = newInfo;
+        }
+
+        public static Collection<ProcessError> fromCollection(Collection<ProcessErrorStateInfo> in)
+                {
+            if (in == null) {
+                return null;
+            }
+
+            List<ProcessError> out = new ArrayList<ProcessError>(in.size());
+            for (ProcessErrorStateInfo info : in) {
+                out.add(new ProcessError(info));
+            }
+            return out;
+        }
+
+        private boolean strEquals(String a, String b) {
+            if ((a == null) && (b == null)) {
+                return true;
+            } else if ((a == null) || (b == null)) {
+                return false;
+            } else {
+                return a.equals(b);
+            }
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) return false;
+            if (!(other instanceof ProcessError)) return false;
+            ProcessError peOther = (ProcessError) other;
+
+            return (info.condition == peOther.info.condition)
+                    && strEquals(info.longMsg, peOther.info.longMsg)
+                    && (info.pid == peOther.info.pid)
+                    && strEquals(info.processName, peOther.info.processName)
+                    && strEquals(info.shortMsg, peOther.info.shortMsg)
+                    && strEquals(info.stackTrace, peOther.info.stackTrace)
+                    && strEquals(info.tag, peOther.info.tag)
+                    && (info.uid == peOther.info.uid);
+        }
+
+        private int hash(Object obj) {
+            if (obj == null) {
+                return 13;
+            } else {
+                return obj.hashCode();
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int code = 17;
+            code += info.condition;
+            code *= hash(info.longMsg);
+            code += info.pid;
+            code *= hash(info.processName);
+            code *= hash(info.shortMsg);
+            code *= hash(info.stackTrace);
+            code *= hash(info.tag);
+            code += info.uid;
+            return code;
+        }
+    }
+}
diff --git a/app-tests/README b/app-tests/README
new file mode 100644
index 0000000..248e72d
--- /dev/null
+++ b/app-tests/README
@@ -0,0 +1,2 @@
+Contains generic Android target tests that can be run against any application.
+
diff --git a/uiautomator/api/current.txt b/uiautomator/api/current.txt
index a1d80c4..664fbbc 100644
--- a/uiautomator/api/current.txt
+++ b/uiautomator/api/current.txt
@@ -11,12 +11,14 @@
   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();
@@ -66,11 +68,14 @@
     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;
@@ -89,13 +94,18 @@
     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 void multiPointerGesture(android.view.MotionEvent.PointerCoords...);
+    method public void pinchIn(int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
+    method public void 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 void twoPointerGesture(android.graphics.Point, android.graphics.Point, android.graphics.Point, android.graphics.Point, int);
     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 long WAIT_FOR_EVENT_TMEOUT = 3000L; // 0xbb8L
     field protected static final long WAIT_FOR_SELECTOR_POLL = 1000L; // 0x3e8L
@@ -140,6 +150,7 @@
 
   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);
@@ -160,6 +171,7 @@
     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 scrollable(boolean);
     method public com.android.uiautomator.core.UiSelector selected(boolean);
     method public com.android.uiautomator.core.UiSelector text(java.lang.String);
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..13d7f36 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,19 @@
 
 package com.android.commands.uiautomator;
 
-import android.accessibilityservice.UiTestAutomationBridge;
+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
@@ -57,21 +62,33 @@
         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;
+            }
+
+            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();
         }
-        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/instrumentation/Android.mk b/uiautomator/instrumentation/Android.mk
new file mode 100644
index 0000000..ed20993
--- /dev/null
+++ b/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
+# below to be uncommented once core-src is using public API only
+#LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/InstrumentationUiAutomatorBridge.java
new file mode 100644
index 0000000..1d4fb93
--- /dev/null
+++ b/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/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorInstrumentationTestRunner.java
new file mode 100644
index 0000000..ae763f2
--- /dev/null
+++ b/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/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
new file mode 100644
index 0000000..eaaef14
--- /dev/null
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -0,0 +1,72 @@
+/*
+ * 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.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;
+
+    /**
+     * 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 params Instrumentation arguments.
+     */
+    void initialize(Bundle params) {
+        mParams = params;
+        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/uiautomator/library/Android.mk b/uiautomator/library/Android.mk
index d4b745a..cd0b5a1 100644
--- a/uiautomator/library/Android.mk
+++ b/uiautomator/library/Android.mk
@@ -16,7 +16,8 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-uiautomator.core_src_files := $(call all-java-files-under, src)
+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
@@ -34,7 +35,8 @@
 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)/src
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \
+	$(LOCAL_PATH)/testrunner-src
 LOCAL_DROIDDOC_HTML_DIR :=
 
 LOCAL_DROIDDOC_OPTIONS:= \
diff --git a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
similarity index 88%
rename from uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index 10878e3..63c51e8 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -16,21 +16,19 @@
 
 package com.android.uiautomator.core;
 
-import android.hardware.display.DisplayManagerGlobal;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Xml;
-import android.view.Display;
 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;
 
-import org.xmlpull.v1.XmlSerializer;
-
 /**
  *
  * @hide
@@ -46,9 +44,13 @@
     /**
      * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
      * and generates an xml dump into the /data/local/window_dump.xml
-     * @param info
+     * @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 info) {
+    public static void dumpWindowToFile(AccessibilityNodeInfo root, int rotation,
+            int width, int height) {
         File baseDir = new File(Environment.getDataDirectory(), "local");
         if (!baseDir.exists()) {
             baseDir.mkdir();
@@ -56,8 +58,9 @@
             baseDir.setWritable(true, false);
             baseDir.setReadable(true, false);
         }
-        dumpWindowToFile(info, new File(
-                new File(Environment.getDataDirectory(), "local"), "window_dump.xml"));
+        dumpWindowToFile(root,
+                new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
+                rotation, width, height);
     }
 
     /**
@@ -65,8 +68,12 @@
      * 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) {
+    public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
+            int width, int height) {
         if (root == null) {
             return;
         }
@@ -78,10 +85,8 @@
             serializer.setOutput(stringWriter);
             serializer.startDocument("UTF-8", true);
             serializer.startTag("", "hierarchy");
-            serializer.attribute("", "rotation", Integer.toString(
-                    DisplayManagerGlobal.getInstance().getRealDisplay(
-                            Display.DEFAULT_DISPLAY).getRotation()));
-            dumpNodeRec(root, serializer, 0);
+            serializer.attribute("", "rotation", Integer.toString(rotation));
+            dumpNodeRec(root, serializer, 0, width, height);
             serializer.endTag("", "hierarchy");
             serializer.endDocument();
             writer.write(stringWriter.toString());
@@ -93,13 +98,14 @@
         Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
     }
 
-    private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,
-            int index) throws IOException {
+    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()));
@@ -113,14 +119,14 @@
         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).toShortString());
+        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);
+                    dumpNodeRec(child, serializer, i, width, height);
                     child.recycle();
                 } else {
                     Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
diff --git a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java b/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
similarity index 79%
rename from uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
index d66e408..54835e3 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
@@ -15,10 +15,7 @@
  */
 package com.android.uiautomator.core;
 
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManagerGlobal;
-import android.view.Display;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 /**
@@ -31,9 +28,11 @@
      * 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) {
+    static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
         if (node == null) {
             return null;
         }
@@ -42,14 +41,10 @@
         node.getBoundsInScreen(nodeRect);
 
         Rect displayRect = new Rect();
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
-        Point outSize = new Point();
-        display.getSize(outSize);
         displayRect.top = 0;
         displayRect.left = 0;
-        displayRect.right = outSize.x;
-        displayRect.bottom = outSize.y;
+        displayRect.right = width;
+        displayRect.bottom = height;
 
         nodeRect.intersect(displayRect);
         return nodeRect;
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
new file mode 100644
index 0000000..a00f2ea
--- /dev/null
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -0,0 +1,758 @@
+/*
+ * 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.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);
+
+    // 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 static final long DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS = 200;
+
+    private final KeyCharacterMap mKeyCharacterMap =
+            KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+    private final UiAutomatorBridge mUiAutomatorBridge;
+
+    private static final long REGULAR_CLICK_LENGTH = 100;
+
+    private long mDownTime;
+
+    private static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000;
+
+    // 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 Predicate<AccessibilityEvent> {
+        int mMask;
+        WaitForAnyEventPredicate(int mask) {
+            mMask = mask;
+        }
+        @Override
+        public boolean apply(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 Predicate<AccessibilityEvent> {
+        int mMask;
+        List<AccessibilityEvent> mEventsList;
+
+        EventCollectingPredicate(int mask, List<AccessibilityEvent> events) {
+            mMask = mask;
+            mEventsList = events;
+        }
+
+        @Override
+        public boolean apply(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 Predicate<AccessibilityEvent> {
+        int mMask;
+        WaitForAllEventPredicate(int mask) {
+            mMask = mask;
+        }
+
+        @Override
+        public boolean apply(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,
+            Predicate<AccessibilityEvent> 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
+     * @return true if events are received, else false if timeout.
+     */
+    public boolean clickAndSync(final int x, final int y) {
+
+        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), WAIT_FOR_EVENT_TMEOUT) != 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
+     * @return true if both events occurred in the expected order
+     */
+    public boolean clickAndWaitForNewWindow(final int x, final int y) {
+        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), WAIT_FOR_EVENT_TMEOUT) != 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),
+                DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS);
+
+        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) {
+            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;
+                }
+            }
+        }
+        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.
+     * @since API Level 18
+     */
+    public void generateMultiPointerGesture(PointerCoords[] ... touches) {
+        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);
+        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);
+            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);
+
+            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);
+            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);
+        injectEventSync(event);
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java b/uiautomator/library/core-src/com/android/uiautomator/core/QueryController.java
similarity index 98%
rename from uiautomator/library/src/com/android/uiautomator/core/QueryController.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/QueryController.java
index 0af603a..6931528 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
+++ b/uiautomator/library/core-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/Tracer.java b/uiautomator/library/core-src/com/android/uiautomator/core/Tracer.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/core/Tracer.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/Tracer.java
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
new file mode 100644
index 0000000..3b519ed
--- /dev/null
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
@@ -0,0 +1,130 @@
+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 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 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,
+            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();
+            }
+        } 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/UiCollection.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiCollection.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/core/UiCollection.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiCollection.java
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
similarity index 85%
rename from uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b668bea..4d8016e 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -16,18 +16,14 @@
 
 package com.android.uiautomator.core;
 
+import android.app.UiAutomation;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Point;
-import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
@@ -40,8 +36,6 @@
 import com.android.internal.util.Predicate;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -70,14 +64,20 @@
     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(UiAutomatorBridge uiAutomatorBridge) {
+        mUiAutomationBridge = uiAutomatorBridge;
     }
 
     boolean isInWatcherContext() {
@@ -86,11 +86,12 @@
 
     /**
      * Provides access the {@link QueryController} and {@link InteractionController}
-     * @return {@link UiAutomatorBridge}
+     * @return {@link ShellUiAutomatorBridge}
      */
     UiAutomatorBridge getAutomatorBridge() {
         return mUiAutomationBridge;
     }
+
     /**
      * Retrieves a singleton instance of UiDevice
      *
@@ -98,27 +99,27 @@
      * @since API Level 16
      */
     public static UiDevice getInstance() {
-        if (mDevice == null) {
-            mDevice = new UiDevice();
+        if (sDevice == null) {
+            sDevice = new UiDevice();
         }
-        return mDevice;
+        return sDevice;
     }
 
     /**
      * Returns the display size in dp (device-independent pixel)
      *
-     * The returned display size is adjusted per screen rotation
+     * 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
-     * @hide
      */
     public Point getDisplaySizeDp() {
         Tracer.trace();
-        Display display = getDefaultDisplay();
+        Display display = mUiAutomationBridge.getDefaultDisplay();
         Point p = new Point();
-        display.getSize(p);
+        display.getRealSize(p);
         DisplayMetrics metrics = new DisplayMetrics();
-        display.getMetrics(metrics);
+        display.getRealMetrics(metrics);
         float dpx = p.x / metrics.density;
         float dpy = p.y / metrics.density;
         p.x = Math.round(dpx);
@@ -345,7 +346,7 @@
      */
     public int getDisplayWidth() {
         Tracer.trace();
-        Display display = getDefaultDisplay();
+        Display display = mUiAutomationBridge.getDefaultDisplay();
         Point p = new Point();
         display.getSize(p);
         return p.x;
@@ -359,7 +360,7 @@
      */
     public int getDisplayHeight() {
         Tracer.trace();
-        Display display = getDefaultDisplay();
+        Display display = mUiAutomationBridge.getDefaultDisplay();
         Point p = new Point();
         display.getSize(p);
         return p.y;
@@ -378,7 +379,7 @@
         if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
             return (false);
         }
-        return getAutomatorBridge().getInteractionController().click(x, y);
+        return getAutomatorBridge().getInteractionController().clickNoSync(x, y);
     }
 
     /**
@@ -397,7 +398,26 @@
     public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
         Tracer.trace(startX, startY, endX, endY, steps);
         return mUiAutomationBridge.getInteractionController()
-                .scrollSwipe(startX, startY, endX, endY, steps);
+                .swipe(startX, startY, endX, endY, steps);
+    }
+
+    /**
+     * 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 18
+     */
+    public boolean drag(int startX, int startY, int endX, int endY, int steps) {
+        Tracer.trace(startX, startY, endX, endY, steps);
+        return mUiAutomationBridge.getInteractionController()
+                .swipe(startX, startY, endX, endY, steps, true);
     }
 
     /**
@@ -574,9 +594,9 @@
      */
     public boolean isNaturalOrientation() {
         Tracer.trace();
-        Display display = getDefaultDisplay();
-        return display.getRotation() == Surface.ROTATION_0 ||
-                display.getRotation() == Surface.ROTATION_180;
+        int ret = mUiAutomationBridge.getRotation();
+        return ret == UiAutomation.ROTATION_FREEZE_0 ||
+                ret == UiAutomation.ROTATION_FREEZE_180;
     }
 
     /**
@@ -585,7 +605,8 @@
      */
     public int getDisplayRotation() {
         Tracer.trace();
-        return getDefaultDisplay().getRotation();
+        waitForIdle();
+        return mUiAutomationBridge.getRotation();
     }
 
     /**
@@ -706,9 +727,12 @@
         AccessibilityNodeInfo root =
                 getAutomatorBridge().getQueryController().getAccessibilityRootNode();
         if(root != null) {
-            AccessibilityNodeInfoDumper.dumpWindowToFile(
-                    root, new File(new File(Environment.getDataDirectory(),
-                            "local/tmp"), fileName));
+            Display display = mUiAutomationBridge.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);
         }
     }
 
@@ -759,25 +783,6 @@
         return true;
     }
 
-    private static Display getDefaultDisplay() {
-        return DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
-    }
-
-    /**
-     * @return the current display rotation in degrees
-     */
-    private static float getDegreesForRotation(int value) {
-        switch (value) {
-        case Surface.ROTATION_90:
-            return 360f - 90f;
-        case Surface.ROTATION_180:
-            return 360f - 180f;
-        case Surface.ROTATION_270:
-            return 360f - 270f;
-        }
-        return 0f;
-    }
-
     /**
      * Take a screenshot of current window and store it as PNG
      *
@@ -806,66 +811,6 @@
      */
     public boolean takeScreenshot(File storePath, float scale, int quality) {
         Tracer.trace(storePath, scale, quality);
-        // This is from com.android.systemui.screenshot.GlobalScreenshot#takeScreenshot
-        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
-        // only in the natural orientation of the device :!)
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        Display display = getDefaultDisplay();
-        display.getRealMetrics(displayMetrics);
-        float[] dims = {displayMetrics.widthPixels, displayMetrics.heightPixels};
-        float degrees = getDegreesForRotation(display.getRotation());
-        boolean requiresRotation = (degrees > 0);
-        Matrix matrix = new Matrix();
-        matrix.reset();
-        if (scale != 1.0f) {
-            matrix.setScale(scale, scale);
-        }
-        if (requiresRotation) {
-            // Get the dimensions of the device in its native orientation
-            matrix.preRotate(-degrees);
-        }
-        matrix.mapPoints(dims);
-        dims[0] = Math.abs(dims[0]);
-        dims[1] = Math.abs(dims[1]);
-
-        // Take the screenshot
-        Bitmap screenShot = Surface.screenshot((int) dims[0], (int) dims[1]);
-        if (screenShot == null) {
-            return false;
-        }
-
-        if (requiresRotation) {
-            // Rotate the screenshot to the current orientation
-            int width = displayMetrics.widthPixels;
-            int height = displayMetrics.heightPixels;
-            if (scale != 1.0f) {
-                width = Math.round(scale * width);
-                height = Math.round(scale * height);
-            }
-            Bitmap ss = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            Canvas c = new Canvas(ss);
-            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
-            c.rotate(degrees);
-            c.translate(-dims[0] / 2, -dims[1] / 2);
-            c.drawBitmap(screenShot, 0, 0, null);
-            c.setBitmap(null);
-            screenShot = ss;
-        }
-
-        // Optimizations
-        screenShot.setHasAlpha(false);
-
-        try {
-            FileOutputStream fos = new FileOutputStream(storePath);
-            screenShot.compress(Bitmap.CompressFormat.PNG, quality, fos);
-            fos.flush();
-            fos.close();
-        } catch (IOException ioe) {
-            Log.e(LOG_TAG, "failed to save screen shot to file", ioe);
-            return false;
-        } finally {
-            screenShot.recycle();
-        }
-        return true;
+        return mUiAutomationBridge.takeScreenshot(storePath, quality);
     }
 }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiObject.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
similarity index 74%
rename from uiautomator/library/src/com/android/uiautomator/core/UiObject.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
index 2bf6455..8e82a98 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiObject.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
@@ -16,11 +16,12 @@
 
 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.accessibility.AccessibilityEvent;
+import android.view.MotionEvent.PointerCoords;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 /**
@@ -47,13 +48,17 @@
      **/
     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
      **/
     protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000;
     /**
-     * @since API Level 16
+     * @since API Level 18
      **/
-    protected static final int SWIPE_MARGIN_LIMIT = 5;
+    protected static final int FINGER_TOUCH_HALF_WIDTH = 20;
 
     private final UiSelector mSelector;
     private final UiAutomatorBridge mUiAutomationBridge;
@@ -183,6 +188,42 @@
     }
 
     /**
+     * Performs a drag of this object to a destination UiObject. Note that the number of steps
+     * used can influence the drag speed and varying speeds may impact the results. Consider
+     * evaluating different speeds when testing this method.
+     *
+     * @param destObj
+     * @param steps usually 40 steps. More or less to change the speed.
+     * @return true of 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);
+    }
+
+    /**
+     * Performs a drag of this object to arbitrary coordinates. Note that the number of steps
+     * used will influence the drag speed and varying speeds may impact the results. Consider
+     * evaluating different speeds when testing this method.
+     *
+     * @param destX
+     * @param destY
+     * @param steps
+     * @return true of 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);
+    }
+
+    /**
      * Perform the action on the UI element that is represented by this UiObject. Also see
      * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
      * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}.
@@ -285,7 +326,9 @@
         }
 
         // targeted node's bounds
-        Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node);
+        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);
@@ -296,7 +339,7 @@
 
         // Scrollable parent's visible bounds
         Rect parentRect = AccessibilityNodeInfoHelper
-                .getVisibleBoundsInScreen(scrollableParentNode);
+                .getVisibleBoundsInScreen(scrollableParentNode, w, h);
         // adjust for partial clipping of targeted by parent node if required
         nodeRect.intersect(parentRect);
         return nodeRect;
@@ -337,9 +380,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().clickAndWaitForEvents(rect.centerX(), rect.centerY(),
-                WAIT_FOR_EVENT_TMEOUT, false, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED +
-                AccessibilityEvent.TYPE_VIEW_SELECTED);
+        return getInteractionController().clickAndSync(rect.centerX(), rect.centerY());
     }
 
     /**
@@ -378,8 +419,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().clickAndWaitForNewWindow(
-                rect.centerX(), rect.centerY(), timeout);
+        return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY());
     }
 
     /**
@@ -396,7 +436,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().click(rect.left + 5, rect.top + 5);
+        return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5);
     }
 
     /**
@@ -413,7 +453,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().longTap(rect.right - 5, rect.bottom - 5);
+        return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5);
     }
 
     /**
@@ -430,7 +470,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().click(rect.right - 5, rect.bottom - 5);
+        return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5);
     }
 
     /**
@@ -447,7 +487,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().longTap(rect.centerX(), rect.centerY());
+        return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY());
     }
 
     /**
@@ -464,7 +504,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        return getInteractionController().longTap(rect.left + 5, rect.top + 5);
+        return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5);
     }
 
     /**
@@ -486,6 +526,24 @@
     }
 
     /**
+     * Reads 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 could be found
+     * @since API Level 18
+     */
+    public String getClassName() throws UiObjectNotFoundException {
+        Tracer.trace();
+        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
+        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"
@@ -551,7 +609,7 @@
             throw new UiObjectNotFoundException(getSelector().toString());
         }
         Rect rect = getVisibleBounds(node);
-        getInteractionController().longTap(rect.left + 20, rect.centerY());
+        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))
@@ -832,4 +890,175 @@
             return "";
         return cs.toString();
     }
+
+    /**
+     * PinchOut generates a 2 pointer gesture where each pointer is moving from the center out
+     * away from each other diagonally towards the edges of the current UI element represented by
+     * this UiObject.
+     * @param percent of the object's diagonal length to use for the pinch
+     * @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.
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void 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(WAIT_FOR_SELECTOR_TIMEOUT);
+        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());
+
+        twoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
+    }
+
+    /**
+     * PinchIn generates a 2 pointer gesture where each pointer is moving towards the other
+     * diagonally from the edges of the current UI element represented by this UiObject, until the
+     * center.
+     * @param percent of the object's diagonal length to use for the pinch
+     * @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.
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void 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(WAIT_FOR_SELECTOR_TIMEOUT);
+        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());
+
+        twoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
+    }
+
+    /**
+     * Generates a 2 pointer gesture from an 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 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.
+     * @since API Level 18
+     */
+    public void twoPointerGesture(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;
+
+        multiPointerGesture(points1, points2);
+    }
+
+    /**
+     * 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.
+     * @since API Level 18
+     */
+    public void multiPointerGesture(PointerCoords[] ...touches) {
+        getInteractionController().generateMultiPointerGesture(touches);
+    }
 }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiObjectNotFoundException.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/core/UiObjectNotFoundException.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiObjectNotFoundException.java
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java
similarity index 94%
rename from uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java
index 8963c38..d16aff7 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java
@@ -58,6 +58,8 @@
     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;
 
     private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>();
 
@@ -279,6 +281,17 @@
     }
 
     /**
+     * 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 widget by its node
      * index in the layout hierarchy.
      *
@@ -458,6 +471,25 @@
     }
 
     /**
+     * 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.
@@ -638,6 +670,11 @@
                     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;
@@ -734,6 +771,11 @@
                     return false;
                 }
                 break;
+            case UiSelector.SELECTOR_RESOURCE_ID:
+                if (node.getViewIdResourceName() != getString(criterion)) {
+                    return false;
+                }
+                break;
             }
         }
         return matchOrUpdateInstance();
@@ -896,6 +938,9 @@
             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;
@@ -941,6 +986,9 @@
             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;
             default:
                 builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i));
             }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiWatcher.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiWatcher.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/core/UiWatcher.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/UiWatcher.java
diff --git a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
deleted file mode 100644
index c97d5de..0000000
--- a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * 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.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;
-import android.view.InputEvent;
-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;
-
-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 static final long DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS = 500;
-
-    private final KeyCharacterMap mKeyCharacterMap =
-            KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-
-    private final UiAutomatorBridge mUiAutomatorBridge;
-
-    private final IWindowManager mWindowManager;
-
-    private final long mLongPressTimeout;
-
-    private static final long REGULAR_CLICK_LENGTH = 100;
-
-    private long mDownTime;
-
-    public InteractionController(UiAutomatorBridge bridge) {
-        mUiAutomatorBridge = bridge;
-
-        // Obtain the window manager.
-        mWindowManager = IWindowManager.Stub.asInterface(
-                ServiceManager.getService(Context.WINDOW_SERVICE));
-        if (mWindowManager == null) {
-            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(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;
-    }
-
-    /**
-     * Click at coordinates and blocks until the first specified accessibility event.
-     *
-     * All clicks will cause some UI change to occur. If the device is busy, this will
-     * block until the device begins to process the click at which point the call returns
-     * and normal wait for idle processing may begin. If no evens are detected for the
-     * timeout period specified, the call will return anyway.
-     * @param x
-     * @param y
-     * @param timeout
-     * @param eventType is an {@link AccessibilityEvent} type
-     * @return True if busy state is detected else false for timeout waiting for busy state
-     */
-    public boolean clickAndWaitForEvent(final int x, final int y, long timeout,
-            final int eventType) {
-        return clickAndWaitForEvents(x, y, timeout, false, eventType);
-    }
-
-    /**
-     * Click at coordinates and blocks until the specified accessibility events. It is possible to
-     * set the wait for all events to occur, in no specific order, or to the wait for any.
-     *
-     * @param x
-     * @param y
-     * @param timeout
-     * @param waitForAll boolean to indicate whether to wait for any or all events
-     * @param eventTypes mask
-     * @return true if events are received, else false if timeout.
-     */
-    public boolean clickAndWaitForEvents(final int x, final int y, long timeout,
-            boolean waitForAll, int eventTypes) {
-        String logString = String.format("clickAndWaitForEvents(%d, %d, %d, %s, %d)", x, y, timeout,
-                Boolean.toString(waitForAll), eventTypes);
-        Log.d(LOG_TAG, logString);
-
-        mUiAutomatorBridge.setOperationTime();
-        Runnable command = new Runnable() {
-            @Override
-            public void run() {
-                if(touchDown(x, y)) {
-                    SystemClock.sleep(REGULAR_CLICK_LENGTH);
-                    touchUp(x, y);
-                }
-            }
-        };
-        return runAndWaitForEvents(command, timeout, waitForAll, eventTypes) != null;
-    }
-
-    /**
-     * Runs a command and waits for a specific accessibility event.
-     * @param command is a Runnable to execute before waiting for the event.
-     * @param timeout
-     * @param eventType
-     * @return The AccessibilityEvent if one is received, otherwise null.
-     */
-    private AccessibilityEvent runAndWaitForEvent(Runnable command, long timeout, int eventType) {
-        return runAndWaitForEvents(command, timeout, false, eventType);
-    }
-
-    /**
-     * Runs a command and waits for accessibility events. It is possible to set the wait for all
-     * events to occur at least once for each, or wait for any one to occur at least once.
-     *
-     * @param command
-     * @param timeout
-     * @param waitForAll boolean to indicate whether to wait for any or all events
-     * @param eventTypesMask
-     * @return The AccessibilityEvent if one is received, otherwise null.
-     */
-    private AccessibilityEvent runAndWaitForEvents(Runnable command, long timeout,
-            final boolean waitForAll, final int eventTypesMask) {
-        if (eventTypesMask == 0)
-            throw new IllegalArgumentException("events mask cannot be zero");
-
-        class EventPredicate implements Predicate<AccessibilityEvent> {
-            int mMask;
-            EventPredicate(int mask) {
-                mMask = mask;
-            }
-            @Override
-            public boolean apply(AccessibilityEvent t) {
-                // check current event in the list
-                if ((t.getEventType() & mMask) != 0) {
-                    if (!waitForAll)
-                        return true;
-
-                    // 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;
-                }
-                // not one of our events
-                return false;
-            }
-        }
-
-        AccessibilityEvent event = null;
-        try {
-            event = mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command,
-                    new EventPredicate(eventTypesMask), timeout);
-        } catch (TimeoutException e) {
-            Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events: " + eventTypesMask);
-            return null;
-        } catch (Exception e) {
-            Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
-            return null;
-        }
-        return event;
-    }
-
-    /**
-     * 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) {
-        mUiAutomatorBridge.setOperationTime();
-        Runnable command = new Runnable() {
-            @Override
-            public void run() {
-                final long eventTime = SystemClock.uptimeMillis();
-                KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN,
-                        keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
-                        InputDevice.SOURCE_KEYBOARD, null);
-                if (injectEventSync(downEvent)) {
-                    KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP,
-                            keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
-                            InputDevice.SOURCE_KEYBOARD, null);
-                    injectEventSync(upEvent);
-                }
-            }
-        };
-
-        return runAndWaitForEvent(command, timeout, eventType) != 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 click(int x, int y) {
-        Log.d(LOG_TAG, "click (" + x + ", " + y + ")");
-        mUiAutomatorBridge.setOperationTime();
-
-        if (touchDown(x, y)) {
-            SystemClock.sleep(REGULAR_CLICK_LENGTH);
-            if (touchUp(x, y))
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * 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
-     * @return true if both events occurred in the expected order
-     */
-    public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) {
-        return (clickAndWaitForEvents(x, y, timeout, true,
-                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED +
-                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED));
-    }
-
-    public boolean longTap(int x, int y) {
-        if (DEBUG) {
-            Log.d(LOG_TAG, "longTap (" + x + ", " + y + ")");
-        }
-
-        mUiAutomatorBridge.setOperationTime();
-        if (touchDown(x, y)) {
-            SystemClock.sleep(mLongPressTimeout);
-            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);
-            }
-        };
-
-        AccessibilityEvent event = runAndWaitForEvent(command,
-                DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS, AccessibilityEvent.TYPE_VIEW_SCROLLED);
-        if (event == null) {
-            return false;
-        }
-        // AdapterViews have indices we can use to check for the beginning.
-        if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
-            boolean foundEnd = event.getFromIndex() == 0 ||
-                    (event.getItemCount() - 1) == event.getToIndex();
-            Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
-            return !foundEnd;
-        } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
-            // Determine if we are scrolling vertically or horizontally.
-            if (downX == upX) {
-                // Vertical
-                boolean foundEnd = event.getScrollY() == 0 ||
-                        event.getScrollY() == event.getMaxScrollY();
-                Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
-                return !foundEnd;
-            } else if (downY == upY) {
-                // Horizontal
-                boolean foundEnd = event.getScrollX() == 0 ||
-                        event.getScrollX() == event.getMaxScrollX();
-                Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
-                return !foundEnd;
-            }
-        }
-        return event != null;
-    }
-
-    /**
-     * 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) {
-        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);
-        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(5);
-        }
-        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(5);
-                }
-            }
-        }
-        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 + ")");
-        }
-
-        mUiAutomatorBridge.setOperationTime();
-        KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
-        if (events != null) {
-            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;
-                }
-            }
-        }
-        return true;
-    }
-
-    public boolean sendKey(int keyCode, int metaState) {
-        if (DEBUG) {
-            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,
-                InputDevice.SOURCE_KEYBOARD, null);
-        if (injectEventSync(downEvent)) {
-            KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP,
-                    keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
-                    InputDevice.SOURCE_KEYBOARD, null);
-            if(injectEventSync(upEvent)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if the device is in its natural orientation. This is determined by
-     * checking whether the orientation is at 0 or 180 degrees.
-     * @return true if it is in natural orientation
-     * @throws RemoteException
-     */
-    public boolean isNaturalRotation() throws RemoteException {
-        return mWindowManager.getRotation() == Surface.ROTATION_0
-                || mWindowManager.getRotation() == Surface.ROTATION_180;
-    }
-
-    /**
-     * 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() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_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() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_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() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_0);
-    }
-
-    /**
-     * Disables the sensors and freezes the device rotation at its
-     * current rotation state.
-     * @throws RemoteException
-     */
-    public void freezeRotation() throws RemoteException {
-        mWindowManager.freezeRotation(-1);
-    }
-
-    /**
-     * 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() throws RemoteException {
-        mWindowManager.thawRotation();
-    }
-
-    /**
-     * 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 {
-        IPowerManager pm =
-                IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
-        return pm.isScreenOn();
-    }
-
-    private static boolean injectEventSync(InputEvent event) {
-        return InputManager.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
-    }
-}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java b/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java
deleted file mode 100644
index 90aa4df..0000000
--- a/uiautomator/library/src/com/android/uiautomator/core/UiAutomatorBridge.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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 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.util.concurrent.TimeoutException;
-
-class UiAutomatorBridge extends UiTestAutomationBridge {
-
-    private static final String LOGTAG = 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 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...
-    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 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() {
-        mInteractionController = new InteractionController(this);
-        mQueryController = new QueryController(this);
-        connect();
-    }
-
-    InteractionController getInteractionController() {
-        return mInteractionController;
-    }
-
-    QueryController getQueryController() {
-        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);
-    }
-
-
-    void addAccessibilityEventListener(AccessibilityEventListener listener) {
-        mListeners.add(listener);
-    }
-
-    private void notifyListeners(AccessibilityEvent event) {
-        for (AccessibilityEventListener listener : mListeners) {
-            listener.onAccessibilityEvent(event);
-        }
-    }
-
-    @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 void waitForIdle() {
-        waitForIdle(TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE);
-    }
-
-    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();
-        }
-    }
-
-    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.");
-            }
-            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();
-                }
-            }
-        }
-    }
-}
diff --git a/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
new file mode 100644
index 0000000..1afa513
--- /dev/null
+++ b/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.isScreenOn();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error getting screen status", e);
+            throw new RuntimeException(e);
+        }
+        return ret;
+    }
+}
diff --git a/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
new file mode 100644
index 0000000..bb97f36
--- /dev/null
+++ b/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -0,0 +1,39 @@
+package com.android.uiautomator.core;
+
+import android.app.UiAutomation;
+import android.app.UiAutomationConnection;
+import android.os.HandlerThread;
+
+/**
+ * @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();
+    }
+
+    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/testrunner/IAutomationSupport.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/testrunner/IAutomationSupport.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/IAutomationSupport.java
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
similarity index 97%
rename from uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
index 9fc0b0b..cda49f6 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java
+++ b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/TestCaseCollector.java
@@ -16,10 +16,7 @@
 
 package com.android.uiautomator.testrunner;
 
-import junit.framework.AssertionFailedError;
-import junit.framework.Test;
 import junit.framework.TestCase;
-import junit.framework.TestResult;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
similarity index 100%
rename from uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestCaseFilter.java
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
similarity index 95%
rename from uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
index 4f41a5c..1b751da 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
+++ b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -22,12 +22,15 @@
 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 junit.framework.AssertionFailedError;
@@ -54,6 +57,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 +72,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,7 +109,13 @@
         if (mDebug) {
             Debug.waitForDebugger();
         }
+        mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
+        mHandlerThread.setDaemon(true);
+        mHandlerThread.start();
+        UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
+        automationWrapper.connect();
         mUiDevice = UiDevice.getInstance();
+        mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
         List<TestCase> testCases = collector.getTestCases();
         Bundle testRunOutput = new Bundle();
 
@@ -149,6 +162,8 @@
         } finally {
             long runTime = SystemClock.uptimeMillis() - startTime;
             resultPrinter.print(testRunResult, runTime, testRunOutput);
+            automationWrapper.disconnect();
+            mHandlerThread.quit();
         }
     }