Merge "Convert AndroidTestLibTests to mockito from littlemock."
diff --git a/uiautomator/api/current.txt b/uiautomator/api/current.txt
index 1250b9f..a058111 100644
--- a/uiautomator/api/current.txt
+++ b/uiautomator/api/current.txt
@@ -90,13 +90,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
@@ -182,18 +187,6 @@
     method public abstract void sendStatus(int, android.os.Bundle);
   }
 
-  public class OnDeviceUiTestCase extends android.test.AndroidTestCase {
-    ctor public OnDeviceUiTestCase();
-    method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
-    method public android.os.Bundle getParams();
-    method public com.android.uiautomator.core.UiDevice getUiDevice();
-    method public void sleep(long);
-  }
-
-  public class OnDeviceUiTestRunner extends android.test.InstrumentationTestRunner {
-    ctor public OnDeviceUiTestRunner();
-  }
-
   public class UiAutomatorTestCase extends junit.framework.TestCase {
     ctor public UiAutomatorTestCase();
     method public com.android.uiautomator.testrunner.IAutomationSupport getAutomationSupport();
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
similarity index 76%
rename from uiautomator/library/core-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
rename to uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
index a668cf5..e40bd14 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/core/OnDeviceUiAutomatorBridge.java
@@ -19,11 +19,15 @@
 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;
 
-class OnDeviceUiAutomatorBridge extends UiAutomatorBridge {
+/**
+ * @hide
+ */
+public class OnDeviceUiAutomatorBridge extends UiAutomatorBridge {
 
     private final Context mContext;
 
@@ -38,6 +42,18 @@
         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/library/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
similarity index 95%
rename from uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
rename to uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
index dc38d25..00897c4 100644
--- a/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
+++ b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestCase.java
@@ -26,12 +26,13 @@
 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. 
+ * UI automator test case that is executed on the device.
  */
 public class OnDeviceUiTestCase extends AndroidTestCase {
     private static final String DISABLE_IME = "disable_ime";
@@ -51,7 +52,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        UiDevice.getInstance().initialize(getContext(), mUiAutomation);
+        UiDevice.getInstance().initialize(new OnDeviceUiAutomatorBridge(mContext, mUiAutomation));
         mShouldDisableIme = "true".equals(getParams().getString(DISABLE_IME));
         if (mShouldDisableIme) {
             setDummyIme();
diff --git a/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java b/uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
similarity index 100%
rename from uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
rename to uiautomator/instrumentation/testrunner-src/com/android/uiautomator/testrunner/OnDeviceUiTestRunner.java
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
index e712559..cd7df67 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -17,19 +17,17 @@
 package com.android.uiautomator.core;
 
 import android.app.UiAutomation;
-import android.content.Context;
 import android.graphics.Point;
-import android.os.IPowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 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.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.Predicate;
@@ -57,22 +55,15 @@
 
     private final UiAutomatorBridge mUiAutomatorBridge;
 
-    private final IWindowManager mWindowManager;
-
     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?");
-        }
     }
 
     /**
@@ -253,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));
     }
 
@@ -389,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);
@@ -432,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);
                 }
             }
         }
@@ -447,6 +438,7 @@
         }
 
         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
@@ -491,8 +483,8 @@
      * @throws RemoteException
      */
     public boolean isNaturalRotation() throws RemoteException {
-        return mWindowManager.getRotation() == UiAutomation.ROTATION_FREEZE_0
-                || mWindowManager.getRotation() == UiAutomation.ROTATION_FREEZE_180;
+        int ret = mUiAutomatorBridge.getRotation();
+        return ret == UiAutomation.ROTATION_FREEZE_0 || ret == UiAutomation.ROTATION_FREEZE_180;
     }
 
     /**
@@ -583,12 +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 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/core-src/com/android/uiautomator/core/UiAutomatorBridge.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
index 0387a62..a15cc24 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiAutomatorBridge.java
@@ -17,7 +17,10 @@
 import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 
-abstract class UiAutomatorBridge {
+/**
+ * @hide
+ */
+public abstract class UiAutomatorBridge {
 
     private static final String LOG_TAG = UiAutomatorBridge.class.getSimpleName();
 
@@ -79,6 +82,10 @@
         return mUiAutomation.setRotation(rotation);
     }
 
+    public abstract int getRotation();
+
+    public abstract boolean isScreenOn();
+
     public void waitForIdle() {
         waitForIdle(TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE);
     }
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index 910deda..51695f6 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -16,7 +16,6 @@
 
 package com.android.uiautomator.core;
 
-import android.app.UiAutomation;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -82,12 +81,8 @@
     /**
      * @hide
      */
-    public void initialize(Context context, UiAutomation uiAutomation) {
-        if (context == null) {
-            mUiAutomationBridge = new ShellUiAutomatorBridge(uiAutomation);
-        } else {
-            mUiAutomationBridge = new OnDeviceUiAutomatorBridge(context, uiAutomation);
-        }
+    public void initialize(UiAutomatorBridge uiAutomatorBridge) {
+        mUiAutomationBridge = uiAutomatorBridge;
     }
 
     boolean isInWatcherContext() {
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
index 2bf6455..ff49f38 100644
--- a/uiautomator/library/core-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);
     }
 
@@ -832,4 +838,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/core-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
similarity index 73%
rename from uiautomator/library/core-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
index ebf99f2..1afa513 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
+++ b/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -18,24 +18,31 @@
 
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
-import android.app.UiAutomation;
 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;
 
-class ShellUiAutomatorBridge extends UiAutomatorBridge {
+/**
+ * @hide
+ */
+public class ShellUiAutomatorBridge extends UiAutomatorBridge {
 
     private static final String LOG_TAG = ShellUiAutomatorBridge.class.getSimpleName();
 
-    ShellUiAutomatorBridge(UiAutomation uiAutomation) {
+    public ShellUiAutomatorBridge(UiAutomation uiAutomation) {
         super(uiAutomation);
     }
 
@@ -84,4 +91,32 @@
         }
         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/core-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
similarity index 100%
rename from uiautomator/library/core-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
rename to uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
diff --git a/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
index 4405927..1b751da 100644
--- a/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
+++ b/uiautomator/library/testrunner-src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -28,6 +28,7 @@
 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;
@@ -113,8 +114,8 @@
         mHandlerThread.start();
         UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper();
         automationWrapper.connect();
-        UiDevice.getInstance().initialize(null, automationWrapper.getUiAutomation());
         mUiDevice = UiDevice.getInstance();
+        mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
         List<TestCase> testCases = collector.getTestCases();
         Bundle testRunOutput = new Bundle();