am c7739bd5: (-s ours) Reconcile with jb-mr1.1-release - do not merge

# Via The Android Automerger (1) and The Android Open Source Project (1)
* commit 'c7739bd594041037cc8aaa511a6182e875116e05':
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..609c1b3 100644
--- a/uiautomator/api/current.txt
+++ b/uiautomator/api/current.txt
@@ -17,6 +17,7 @@
     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();
@@ -71,6 +72,7 @@
     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 +91,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 +147,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 +168,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..6f5ac1c 100644
--- a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -16,14 +16,16 @@
 
 package com.android.commands.uiautomator;
 
-import android.accessibilityservice.UiTestAutomationBridge;
+import android.app.UiAutomation;
 import android.os.Environment;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.commands.uiautomator.Launcher.Command;
 import com.android.uiautomator.core.AccessibilityNodeInfoDumper;
+import com.android.uiautomator.core.UiAutomationShellWrapper;
 
 import java.io.File;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Implementation of the dump subcommand
@@ -57,21 +59,27 @@
         if (args.length > 0) {
             dumpFile = new File(args[0]);
         }
-        UiTestAutomationBridge bridge = new UiTestAutomationBridge();
-        bridge.connect();
+        UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
+        automationWrapper.connect();
         // It appears that the bridge needs time to be ready. Making calls to the
         // bridge immediately after connecting seems to cause exceptions. So let's also
         // do a wait for idle in case the app is busy.
-        bridge.waitForIdle(1000, 1000 * 10);
-        AccessibilityNodeInfo info = bridge.getRootAccessibilityNodeInfoInActiveWindow();
-        if (info == null) {
-            System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
+        try {
+            UiAutomation uiAutomation = automationWrapper.getUiAutomation();
+            uiAutomation.waitForIdle(1000, 1000 * 10);
+            AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
+            if (info == null) {
+                System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
+                return;
+            }
+            AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile);
+        } catch (TimeoutException re) {
+            System.err.println("ERROR: could not get idle state.");
             return;
+        } finally {
+            automationWrapper.disconnect();
         }
-        AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile);
-        bridge.disconnect();
         System.out.println(
                 String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath()));
     }
-
 }
diff --git a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
index 79428e9..ce55f18 100644
--- a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
+++ b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/EventsCommand.java
@@ -16,10 +16,11 @@
 
 package com.android.commands.uiautomator;
 
-import android.accessibilityservice.UiTestAutomationBridge;
+import android.app.UiAutomation.OnAccessibilityEventListener;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.commands.uiautomator.Launcher.Command;
+import com.android.uiautomator.core.UiAutomationShellWrapper;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -49,15 +50,17 @@
 
     @Override
     public void run(String[] args) {
-        final UiTestAutomationBridge bridge = new UiTestAutomationBridge() {
+        UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
+        automationWrapper.connect();
+        automationWrapper.getUiAutomation().setOnAccessibilityEventListener(
+                new OnAccessibilityEventListener() {
             @Override
             public void onAccessibilityEvent(AccessibilityEvent event) {
                 SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
                 System.out.println(String.format("%s %s",
                         formatter.format(new Date()), event.toString()));
             }
-        };
-        bridge.connect();
+        });
         // there's really no way to stop, essentially we just block indefinitely here and wait
         // for user to press Ctrl+C
         synchronized (mQuitLock) {
@@ -67,5 +70,6 @@
                 e.printStackTrace();
             }
         }
+        automationWrapper.disconnect();
     }
 }
diff --git a/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
new file mode 100644
index 0000000..e40bd14
--- /dev/null
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.uiautomator.core;
+
+import android.app.Service;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.Display;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+/**
+ * @hide
+ */
+public class OnDeviceUiAutomatorBridge extends UiAutomatorBridge {
+
+    private final Context mContext;
+
+    public OnDeviceUiAutomatorBridge(Context context, UiAutomation uiAutomation) {
+        super(uiAutomation);
+        mContext = context;
+    }
+
+    public Display getDefaultDisplay() {
+        WindowManager windowManager = (WindowManager)
+                mContext.getSystemService(Service.WINDOW_SERVICE);
+        return windowManager.getDefaultDisplay();
+    }
+
+    @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/OnDeviceUiTestCase.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
new file mode 100644
index 0000000..00897c4
--- /dev/null
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.uiautomator.testrunner;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.OnDeviceUiAutomatorBridge;
+import com.android.uiautomator.core.UiDevice;
+
+import java.util.List;
+
+/**
+ * UI automator test case that is executed on the device.
+ */
+public class OnDeviceUiTestCase extends AndroidTestCase {
+    private static final String DISABLE_IME = "disable_ime";
+    private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
+    private boolean mShouldDisableIme;
+
+    private UiAutomation mUiAutomation;
+    private Bundle mParams;
+
+    private final IAutomationSupport mAutomationSupport = new IAutomationSupport() {
+        @Override
+        public void sendStatus(int resultCode, Bundle status) {
+            sendStatus(resultCode, status);
+        }
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        UiDevice.getInstance().initialize(new OnDeviceUiAutomatorBridge(mContext, mUiAutomation));
+        mShouldDisableIme = "true".equals(getParams().getString(DISABLE_IME));
+        if (mShouldDisableIme) {
+            setDummyIme();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mShouldDisableIme) {
+            restoreActiveIme();
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Get current instance of {@link UiDevice}. Works similar to calling the static
+     * {@link UiDevice#getInstance()} from anywhere in the test classes.
+     * @since API Level 16
+     */
+    public UiDevice getUiDevice() {
+        return UiDevice.getInstance();
+    }
+
+    /**
+     * Get command line parameters. On the command line when passing <code>-e key value</code>
+     * pairs, the {@link Bundle} will have the key value pairs conveniently available to the
+     * tests.
+     * @since API Level 16
+     */
+    public Bundle getParams() {
+        return mParams;
+    }
+
+    /**
+     * Initializes this test case.
+     *
+     * @param uiAutomation An {@link UiAutomation} instance.
+     * @param params Instrumentation arguments.
+     */
+    void initialize(UiAutomation uiAutomation,  Bundle params) {
+        mUiAutomation = uiAutomation;
+        mParams = params;
+    }
+
+    /**
+     * Provides support for running tests to report interim status
+     *
+     * @return IAutomationSupport
+     * @since API Level 16
+     */
+    public IAutomationSupport getAutomationSupport() {
+        return mAutomationSupport;
+    }
+
+    /**
+     * Calls {@link SystemClock#sleep(long)} to sleep
+     * @param ms is in milliseconds.
+     * @since API Level 16
+     */
+    public void sleep(long ms) {
+        SystemClock.sleep(ms);
+    }
+
+    private void setDummyIme() throws RemoteException {
+        IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
+                .getService(Context.INPUT_METHOD_SERVICE));
+        List<InputMethodInfo> infos = im.getInputMethodList();
+        String id = null;
+        for (InputMethodInfo info : infos) {
+            if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
+                id = info.getId();
+            }
+        }
+        if (id == null) {
+            throw new RuntimeException(String.format(
+                    "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
+        }
+        im.setInputMethod(null, id);
+    }
+
+    private void restoreActiveIme() throws RemoteException {
+        // TODO: figure out a way to restore active IME
+        // Currently retrieving active IME requires querying secure settings provider, which is hard
+        // to do without a Context; so the caveat here is that to make the post test device usable,
+        // the active IME needs to be manually switched.
+    }
+}
diff --git a/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
new file mode 100644
index 0000000..857d7e9
--- /dev/null
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.uiautomator.testrunner;
+
+import android.test.AndroidTestRunner;
+import android.test.InstrumentationTestRunner;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import java.util.List;
+
+/**
+ * Test runner for {@link OnDeviceUiTestCase}s. Such tests are executed
+ * on the device and have access to an applications context.
+ */
+public class OnDeviceUiTestRunner extends InstrumentationTestRunner {
+
+    @Override
+    protected AndroidTestRunner getAndroidTestRunner() {
+        return new AndroidTestRunner() {
+            @Override
+            public void runTest(TestResult testResult) {
+                List<TestCase> testCases = getTestCases();
+                final int testCaseCount = testCases.size();
+                for (int i = 0; i < testCaseCount; i++) {
+                    TestCase testCase = testCases.get(i);
+                    if (testCase instanceof OnDeviceUiTestCase) {
+                        OnDeviceUiTestCase uiTestCase = (OnDeviceUiTestCase) testCase;
+                        uiTestCase.initialize(OnDeviceUiTestRunner.this.getUiAutomation(),
+                                getArguments());
+                    }
+                }
+                super.runTest(testResult);
+            }
+        };
+    }
+}
diff --git a/uiautomator/library/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 98%
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..295bc06 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -100,6 +100,7 @@
             serializer.attribute("", "NAF", Boolean.toString(true));
         serializer.attribute("", "index", Integer.toString(index));
         serializer.attribute("", "text", safeCharSeqToString(node.getText()));
+        serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewId()));
         serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
         serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
         serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
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 100%
rename from uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoHelper.java
diff --git a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
similarity index 78%
rename from uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
rename to uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
index c97d5de..cd7df67 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -16,30 +16,18 @@
 
 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.app.UiAutomation;
 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.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.Predicate;
@@ -67,72 +55,15 @@
 
     private final UiAutomatorBridge mUiAutomatorBridge;
 
-    private final IWindowManager mWindowManager;
-
-    private final long mLongPressTimeout;
-
     private static final long REGULAR_CLICK_LENGTH = 100;
 
     private long mDownTime;
 
+    // Inserted after each motion event injection.
+    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
+
     public InteractionController(UiAutomatorBridge bridge) {
         mUiAutomatorBridge = bridge;
-
-        // 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;
     }
 
     /**
@@ -170,7 +101,6 @@
                 Boolean.toString(waitForAll), eventTypes);
         Log.d(LOG_TAG, logString);
 
-        mUiAutomatorBridge.setOperationTime();
         Runnable command = new Runnable() {
             @Override
             public void run() {
@@ -266,7 +196,6 @@
      */
     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
             final int eventType, long timeout) {
-        mUiAutomatorBridge.setOperationTime();
         Runnable command = new Runnable() {
             @Override
             public void run() {
@@ -295,7 +224,6 @@
      */
     public boolean click(int x, int y) {
         Log.d(LOG_TAG, "click (" + x + ", " + y + ")");
-        mUiAutomatorBridge.setOperationTime();
 
         if (touchDown(x, y)) {
             SystemClock.sleep(REGULAR_CLICK_LENGTH);
@@ -316,7 +244,7 @@
      */
     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_STATE_CHANGED |
                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED));
     }
 
@@ -325,9 +253,8 @@
             Log.d(LOG_TAG, "longTap (" + x + ", " + y + ")");
         }
 
-        mUiAutomatorBridge.setOperationTime();
         if (touchDown(x, y)) {
-            SystemClock.sleep(mLongPressTimeout);
+            SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
             if(touchUp(x, y)) {
                 return true;
             }
@@ -453,7 +380,7 @@
             // 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);
+            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
         }
         ret &= touchUp(upX, upY);
         return(ret);
@@ -496,7 +423,7 @@
                     // 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);
+                    SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
                 }
             }
         }
@@ -510,8 +437,8 @@
             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
@@ -534,7 +461,6 @@
             Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
         }
 
-        mUiAutomatorBridge.setOperationTime();
         final long eventTime = SystemClock.uptimeMillis();
         KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN,
                 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
@@ -557,8 +483,8 @@
      * @throws RemoteException
      */
     public boolean isNaturalRotation() throws RemoteException {
-        return mWindowManager.getRotation() == Surface.ROTATION_0
-                || mWindowManager.getRotation() == Surface.ROTATION_180;
+        int ret = mUiAutomatorBridge.getRotation();
+        return ret == UiAutomation.ROTATION_FREEZE_0 || ret == UiAutomation.ROTATION_FREEZE_180;
     }
 
     /**
@@ -569,8 +495,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationRight() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_270);
+    public void setRotationRight() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
     }
 
     /**
@@ -581,8 +507,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationLeft() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_90);
+    public void setRotationLeft() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
     }
 
     /**
@@ -593,8 +519,8 @@
      * depending on the current physical position of the test device.
      * @throws RemoteException
      */
-    public void setRotationNatural() throws RemoteException {
-        mWindowManager.freezeRotation(Surface.ROTATION_0);
+    public void setRotationNatural() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
     }
 
     /**
@@ -602,8 +528,8 @@
      * current rotation state.
      * @throws RemoteException
      */
-    public void freezeRotation() throws RemoteException {
-        mWindowManager.freezeRotation(-1);
+    public void freezeRotation() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
     }
 
     /**
@@ -611,8 +537,8 @@
      * allowing its contents to rotate with the device physical rotation.
      * @throws RemoteException
      */
-    public void unfreezeRotation() throws RemoteException {
-        mWindowManager.thawRotation();
+    public void unfreezeRotation() {
+        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
     }
 
     /**
@@ -649,13 +575,111 @@
      * @throws RemoteException
      */
     public boolean isScreenOn() throws RemoteException {
-        IPowerManager pm =
-                IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
-        return pm.isScreenOn();
+        return mUiAutomatorBridge.isScreenOn();
     }
 
-    private static boolean injectEventSync(InputEvent event) {
-        return InputManager.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+    private boolean injectEventSync(InputEvent event) {
+        return mUiAutomatorBridge.injectInputEvent(event, true);
+    }
+
+    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..a15cc24
--- /dev/null
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
@@ -0,0 +1,138 @@
+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 connect() {
+        mUiAutomation.connect();
+    }
+
+    public void disconnect() {
+        mUiAutomation.disconnect();
+    }
+
+    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 97%
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..604afa6 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -27,7 +27,6 @@
 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;
@@ -70,14 +69,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 +91,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 +104,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();
         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);
@@ -585,6 +591,7 @@
      */
     public int getDisplayRotation() {
         Tracer.trace();
+        waitForIdle();
         return getDefaultDisplay().getRotation();
     }
 
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 79%
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..07150a1 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiObject.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
@@ -16,10 +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.MotionEvent.PointerCoords;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -47,13 +49,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;
@@ -338,7 +344,7 @@
         }
         Rect rect = getVisibleBounds(node);
         return getInteractionController().clickAndWaitForEvents(rect.centerX(), rect.centerY(),
-                WAIT_FOR_EVENT_TMEOUT, false, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED +
+                WAIT_FOR_EVENT_TMEOUT, false, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
                 AccessibilityEvent.TYPE_VIEW_SELECTED);
     }
 
@@ -486,6 +492,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"
@@ -832,4 +856,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);
+    }
+}
\ No newline at end of file
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..68f37e4 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.getViewId() != 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/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();
         }
     }