Merge "App to test the app launch performance" into mnc-dev
diff --git a/jank/jankmicrobenchmark/Android.mk b/jank/jankmicrobenchmark/Android.mk
new file mode 100644
index 0000000..ea4fe5d
--- /dev/null
+++ b/jank/jankmicrobenchmark/Android.mk
@@ -0,0 +1,26 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# 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_PACKAGE_NAME := JankMicroBenchmarkTests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper launcher-helper-lib
+
+LOCAK_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/jank/jankmicrobenchmark/AndroidManifest.xml b/jank/jankmicrobenchmark/AndroidManifest.xml
new file mode 100644
index 0000000..4ce5e54
--- /dev/null
+++ b/jank/jankmicrobenchmark/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.jankmicrobenchmark.janktests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.jankmicrobenchmark.janktests"
+            android:label="Platform Benchmark Jank Tests" />
+
+</manifest>
diff --git a/jank/jankmicrobenchmark/src/com/android/jankmicrobenchmark/janktests/ApiDemoJankTests.java b/jank/jankmicrobenchmark/src/com/android/jankmicrobenchmark/janktests/ApiDemoJankTests.java
new file mode 100644
index 0000000..3acaded
--- /dev/null
+++ b/jank/jankmicrobenchmark/src/com/android/jankmicrobenchmark/janktests/ApiDemoJankTests.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2015 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.jankmicrobenchmark.janktests;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.widget.Button;
+
+import junit.framework.Assert;
+
+/**
+ * Jank micro benchmark tests
+ * App : ApiDemos
+ */
+
+public class ApiDemoJankTests extends JankTestBase {
+    private static final int LONG_TIMEOUT = 5000;
+    private static final int SHORT_TIMEOUT = 500;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.example.android.apis";
+    private static final String RES_PACKAGE_NAME = "android";
+    private static final String APP_NAME = "API Demos";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory
+                .getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchApiDemosAndSelectAnimation(String optionName)
+            throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        UiObject2 animation = mDevice.wait(Until.findObject(
+                By.res(RES_PACKAGE_NAME, "text1").text("Animation")), LONG_TIMEOUT);
+        Assert.assertNotNull("Animation is null", animation);
+        animation.click();
+        UiObject2 option = mDevice.wait(Until.findObject(
+                By.res(RES_PACKAGE_NAME, "text1").text(optionName)), LONG_TIMEOUT);
+        int maxAttempt = 3;
+        while (option == null && maxAttempt > 0) {
+            mDevice.wait(Until.findObject(By.res(RES_PACKAGE_NAME, "content")), LONG_TIMEOUT)
+                    .scroll(Direction.DOWN, 1.0f);
+            option = mDevice.wait(Until.findObject(By.res(RES_PACKAGE_NAME, "text1")
+                    .text(optionName)), LONG_TIMEOUT);
+            --maxAttempt;
+        }
+        Assert.assertNotNull("Option is null", option);
+        option.click();
+    }
+
+    // Since the app doesn't start at the first page when reloaded after the first time,
+    // ensuring that we head back to the first screen before going Home so we're always
+    // on screen one.
+    public void goBackHome(Bundle metrics) throws UiObjectNotFoundException {
+        String launcherPackage = mDevice.getLauncherPackageName();
+        UiObject2 homeScreen = mDevice.findObject(By.res(launcherPackage,"workspace"));
+        while (homeScreen == null) {
+            mDevice.pressBack();
+            homeScreen = mDevice.findObject(By.res(launcherPackage,"workspace"));
+        }
+        super.afterTest(metrics);
+    }
+
+    // Loads the 'activity transition' animation
+    public void selectActivityTransitionAnimation() throws UiObjectNotFoundException {
+         launchApiDemosAndSelectAnimation("Activity Transition");
+    }
+
+    // Measures jank for activity transition animation
+    @JankTest(beforeTest="selectActivityTransitionAnimation", afterTest="goBackHome",
+        expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testActivityTransitionAnimation() {
+        for (int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 redBallTile = mDevice.findObject(By.res(PACKAGE_NAME, "ball"));
+            redBallTile.click();
+            SystemClock.sleep(LONG_TIMEOUT);
+            mDevice.pressBack();
+        }
+    }
+
+    // Loads the 'view flip' animation
+    public void selectViewFlipAnimation() throws UiObjectNotFoundException {
+        launchApiDemosAndSelectAnimation("View Flip");
+    }
+
+    // Measures jank for view flip animation
+    @JankTest(beforeTest="selectViewFlipAnimation", afterTest="goBackHome",
+        expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testViewFlipAnimation() {
+        for (int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 flipButton = mDevice.findObject(By.res(PACKAGE_NAME, "button"));
+            flipButton.click();
+            SystemClock.sleep(LONG_TIMEOUT);
+        }
+    }
+
+    // Loads the 'cloning' animation
+    public void selectCloningAnimation() throws UiObjectNotFoundException {
+        launchApiDemosAndSelectAnimation("Cloning");
+    }
+
+    // Measures jank for cloning animation
+    @JankTest(beforeTest="selectCloningAnimation", afterTest="goBackHome",
+        expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testCloningAnimation() {
+        for (int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 runCloningButton = mDevice.findObject(By.res(PACKAGE_NAME, "startButton"));
+            runCloningButton.click();
+            SystemClock.sleep(LONG_TIMEOUT);
+        }
+    }
+
+    // Loads the 'loading' animation
+    public void selectLoadingOption() throws UiObjectNotFoundException {
+        launchApiDemosAndSelectAnimation("Loading");
+    }
+
+    // Measures jank for 'loading' animation
+    @JankTest(beforeTest="selectLoadingOption", afterTest="goBackHome",
+              expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testLoadingJank() {
+        UiObject2 runButton = mDevice.wait(Until.findObject(
+            By.res(PACKAGE_NAME, "startButton").text("Run")), LONG_TIMEOUT);
+        Assert.assertNotNull("Run button is null", runButton);
+        for(int i = 0; i < INNER_LOOP; i++) {
+            runButton.click();
+            SystemClock.sleep(SHORT_TIMEOUT * 2);
+        }
+    }
+
+    // Loads the 'simple transition' animation
+    public void selectSimpleTransitionOption() throws UiObjectNotFoundException {
+        launchApiDemosAndSelectAnimation("Simple Transitions");
+    }
+
+    // Measures jank for 'simple transition' animation
+    @JankTest(beforeTest="selectSimpleTransitionOption", afterTest="goBackHome",
+              expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testSimpleTransitionJank() {
+        for(int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 scene2 = mDevice.wait(Until.findObject(
+                    By.res(PACKAGE_NAME, "scene2")), LONG_TIMEOUT);
+            Assert.assertNotNull("Scene2 is null", scene2);
+            scene2.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+
+            UiObject2 scene1 = mDevice.wait(Until.findObject(
+                    By.res(PACKAGE_NAME, "scene1")), LONG_TIMEOUT);
+            Assert.assertNotNull("Scene1 is null", scene1);
+            scene1.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+        }
+    }
+
+    // Loads the 'hide/show' animation
+    public void selectHideShowAnimationOption() throws UiObjectNotFoundException {
+        launchApiDemosAndSelectAnimation("Hide-Show Animations");
+    }
+
+    // Measures jank for 'hide/show' animation
+    @JankTest(beforeTest="selectHideShowAnimationOption", afterTest="goBackHome",
+              expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testHideShowAnimationJank() {
+        for(int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 showButton = mDevice.wait(Until.findObject(By.res(
+                    PACKAGE_NAME, "addNewButton").text("Show Buttons")), LONG_TIMEOUT);
+            Assert.assertNotNull("Show Button is null", showButton);
+            showButton.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+
+            UiObject2 button0 = mDevice.wait(Until.findObject(
+                    By.clazz(Button.class).text("0")), LONG_TIMEOUT);
+            Assert.assertNotNull("Button0 is null", button0);
+            button0.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+
+            UiObject2 button1 = mDevice.wait(Until.findObject(
+                    By.clazz(Button.class).text("1")), LONG_TIMEOUT);
+            Assert.assertNotNull("Button1 is null", button1);
+            button1.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+
+            UiObject2 button2 = mDevice.wait(Until.findObject(
+                    By.clazz(Button.class).text("2")), LONG_TIMEOUT);
+            Assert.assertNotNull("Button2 is null", button2);
+            button2.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+
+            UiObject2 button3 = mDevice.wait(Until.findObject(
+                    By.clazz(Button.class).text("3")), LONG_TIMEOUT);
+            Assert.assertNotNull("Button3 is null", button3);
+            button3.click();
+            SystemClock.sleep(SHORT_TIMEOUT);
+        }
+    }
+}
diff --git a/jank/sysapp/Android.mk b/jank/sysapp/Android.mk
new file mode 100644
index 0000000..66073a7
--- /dev/null
+++ b/jank/sysapp/Android.mk
@@ -0,0 +1,26 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# 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_PACKAGE_NAME := SystemAppJankTests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper launcher-helper-lib
+
+LOCAK_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/jank/sysapp/AndroidManifest.xml b/jank/sysapp/AndroidManifest.xml
new file mode 100644
index 0000000..99e3178
--- /dev/null
+++ b/jank/sysapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.sysapp.janktests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.sysapp.janktests"
+            android:label="SysApp Jank Tests" />
+
+</manifest>
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/BooksJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/BooksJankTests.java
new file mode 100644
index 0000000..0973bc3
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/BooksJankTests.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import junit.framework.Assert;
+
+/**
+ * Jank test for Books app recommendation page fling
+ */
+
+public class BooksJankTests extends JankTestBase {
+    private static final int LONG_TIMEOUT = 1000;
+    private static final int SHORT_TIMEOUT = 1000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.google.android.apps.books";
+    private static final String APP_NAME = "Play Books";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchBooks () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        dismissClings();
+        openMyLibrary();
+        Assert.assertTrue("Books haven't loaded yet", getNumberOfVisibleBooks() > 3);
+    }
+
+    // Measures jank while fling books mylibrary
+    @JankTest(beforeTest="launchBooks", expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testBooksRecommendationPageFling() {
+        UiObject2 container = mDevice.wait(Until.findObject(
+                By.res(PACKAGE_NAME, "content_container")), LONG_TIMEOUT);
+        for (int i = 0; i < INNER_LOOP; i++) {
+          container.scroll(Direction.DOWN, 1.0f);
+          SystemClock.sleep(SHORT_TIMEOUT);
+          container.scroll(Direction.UP, 1.0f);
+        }
+    }
+
+    // All helper methods are at bottom
+    // with the assumptions is that these will have their own library
+    private void dismissClings() {
+        // Dismiss confidentiality warning. It's okay to timeout here.
+        UiObject2 warning = mDevice.wait(
+                Until.findObject(By.clazz(".Button").text("OK")), LONG_TIMEOUT);
+        if (warning != null) {
+            warning. click();
+        }
+        // Close the drawer.
+        UiObject2 close = mDevice.wait(
+                Until.findObject(By.desc("Hide navigation drawer")), LONG_TIMEOUT);
+        if (close != null) {
+            close.click();
+        }
+        // Turn sync off
+        UiObject2 syncoff = mDevice.wait(Until.findObject(
+                By.clazz(Button.class).text("Keep sync off")), LONG_TIMEOUT);
+        if (syncoff != null) {
+            syncoff.click();
+        }
+    }
+
+    public void openNavigationDrawer() {
+      if (!mDevice.hasObject(By.res(PACKAGE_NAME, "play_drawer_container"))) {
+          mDevice.findObject(By.desc("Show navigation drawer")).click();
+          Assert.assertTrue("Failed to open navigation drawer", mDevice.wait(
+              Until.hasObject(By.res(PACKAGE_NAME, "play_drawer_list")), LONG_TIMEOUT));
+
+          // Extra sleep to wait for the drawer to finish sliding in
+          SystemClock.sleep(500);
+      }
+  }
+
+    public void openMyLibrary() {
+        openNavigationDrawer();
+        UiObject2 library = mDevice.wait(
+            Until.findObject(By.text("My Library").res("")), LONG_TIMEOUT);
+        Assert.assertNotNull("Could not find 'My Library' button", library);
+        library.click();
+    }
+
+    public int getNumberOfVisibleBooks() {
+      UiObject2 list = mDevice.wait(
+              Until.findObject(By.res(PACKAGE_NAME, "cards_grid")), LONG_TIMEOUT);
+      Assert.assertNotNull("Failed to locate 'cards_grid'", list);
+      return list.getChildCount();
+    }
+}
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/CameraJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/CameraJankTests.java
new file mode 100644
index 0000000..a1514cc
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/CameraJankTests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.widget.Button;
+
+import junit.framework.Assert;
+
+/**
+ * Jank test for flipping front and back camera n times.
+ */
+
+public class CameraJankTests extends JankTestBase {
+    private static final int TIMEOUT = 3000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.google.android.GoogleCamera";
+    private static final String FRAMEWORK_PACKAGE_NAME = "com.android.camera2";
+    private static final String APP_NAME = "Camera";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchCamera () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        dismissClings();
+        BySelector threeDotsSelector = By.res(
+            FRAMEWORK_PACKAGE_NAME, "mode_options_toggle").desc("Options");
+        UiObject2 threeDots = mDevice.wait(Until.findObject(threeDotsSelector), TIMEOUT);
+        Assert.assertNotNull("Three dot icon is missing", threeDots);
+        threeDots.click();
+        // wait for next window to show up
+        mDevice.wait(Until.gone(threeDotsSelector), TIMEOUT);
+    }
+
+    // Measures jank while fling YouTube recommendation
+    @JankTest(beforeTest="launchCamera", expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testFrontBackCameraFlip() {
+        UiObject2 cameraToggle = null;
+        BySelector cameraToggleSelector = By.res(FRAMEWORK_PACKAGE_NAME, "camera_toggle_button");
+        for (int i = 0; i < INNER_LOOP; i++) {
+          cameraToggle = mDevice.wait(Until.findObject(cameraToggleSelector), 3 * TIMEOUT);
+          Assert.assertNotNull("Camera flipper icon is missing", cameraToggle);
+          cameraToggle.click();
+          mDevice.wait(Until.gone(cameraToggleSelector), TIMEOUT);
+        }
+    }
+
+    private void dismissClings() {
+        // Dismiss tag next screen. It's okay to timeout. These dialog screens might not exist..
+        UiObject2 next = mDevice.wait(Until.findObject(
+                By.clazz(Button.class).text("NEXT")), 2 * TIMEOUT);
+        if (next != null) {
+            next.click();
+        }
+        // Choose sensor size. It's okay to timeout. These dialog screens might not exist..
+        UiObject2 sensor = mDevice.wait(Until.findObject(
+                By.res(PACKAGE_NAME, "confirm_button").text("OK, GOT IT")), 2 * TIMEOUT);
+        if (sensor != null) {
+            sensor.click();
+        }
+        // Dismiss the photo location dialog box if exist.
+        UiObject2 thanks = mDevice.wait(Until.findObject(
+                By.text("No thanks")), 2 * TIMEOUT);
+        if (thanks != null) {
+            thanks.click();
+        }
+        // Dismiss dogfood dialog
+        if (mDevice.wait(Until.hasObject(
+                By.res(PACKAGE_NAME, "internal_release_dialog_title")), 2 * TIMEOUT)) {
+            mDevice.findObject(By.res(FRAMEWORK_PACKAGE_NAME, "ok_button")).click();
+        }
+    }
+}
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/ChromeJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/ChromeJankTests.java
new file mode 100644
index 0000000..7ef6407
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/ChromeJankTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import junit.framework.Assert;
+
+/**
+ * Jank test for Chorme apps
+ * Open overflow menu
+ */
+
+public class ChromeJankTests extends JankTestBase {
+    private static final int SHORT_TIMEOUT = 1000;
+    private static final int LONG_TIMEOUT = 30000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.android.chrome";
+    private static final String APP_NAME = "Chrome";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchChrome () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        mDevice.waitForIdle();
+        // To infer that test is ready to be executed
+        getOverflowMenu();
+    }
+
+    // Measures jank window render for overflow menu tap
+    @JankTest(beforeTest="launchChrome", expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testChromeOverflowMenuTap() {
+        for (int i = 0; i < INNER_LOOP; i++) {
+            UiObject2 overflow = getOverflowMenu();
+            overflow.click();
+            SystemClock.sleep(100);
+            mDevice.pressBack();
+        }
+    }
+
+    public UiObject2 getOverflowMenu() {
+      UiObject2 overflow = mDevice.wait(
+          Until.findObject(By.desc("More options")), 5 * SHORT_TIMEOUT);
+      Assert.assertNotNull("Failed to locate overflow menu", overflow);
+      return overflow;
+    }
+}
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/GMailJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/GMailJankTests.java
new file mode 100644
index 0000000..08cece5
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/GMailJankTests.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import junit.framework.Assert;
+
+/**
+ * Jank test for scrolling gmail inbox mails
+ */
+
+public class GMailJankTests extends JankTestBase {
+    private static final int SHORT_TIMEOUT = 1000;
+    private static final int LONG_TIMEOUT = 30000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.google.android.gm";
+    private static final String APP_NAME = "Gmail";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchGMail () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        dismissClings();
+        // Need any check for account-name??
+        waitForEmailSync();
+    }
+
+    // Measures jank while scrolling gmail inbox
+    @JankTest(beforeTest="launchGMail", expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testGMailInboxFling() {
+        UiObject2 list = mDevice.wait(
+                Until.findObject(By.res(PACKAGE_NAME, "conversation_list_view")), 5000);
+        Assert.assertNotNull("Failed to locate 'conversation_list_view", list);
+        for (int i = 0; i < INNER_LOOP; i++) {
+          list.scroll(Direction.DOWN, 1.0f);
+          SystemClock.sleep(SHORT_TIMEOUT);
+          list.scroll(Direction.UP, 1.0f);
+        }
+    }
+
+    private void dismissClings() {
+        UiObject2 welcomeScreenGotIt = mDevice.wait(
+            Until.findObject(By.res(PACKAGE_NAME, "welcome_tour_got_it")), 2000);
+        if (welcomeScreenGotIt != null) {
+            welcomeScreenGotIt.clickAndWait(Until.newWindow(), SHORT_TIMEOUT);
+        }
+        UiObject2 welcomeScreenSkip = mDevice.wait(
+            Until.findObject(By.res(PACKAGE_NAME, "welcome_tour_skip")), 2000);
+        if (welcomeScreenSkip != null) {
+          welcomeScreenSkip.clickAndWait(Until.newWindow(), SHORT_TIMEOUT);
+        }
+        UiObject2 tutorialDone = mDevice.wait(
+                Until.findObject(By.res(PACKAGE_NAME, "action_done")), 2 * SHORT_TIMEOUT);
+        if (tutorialDone != null) {
+            tutorialDone.clickAndWait(Until.newWindow(), SHORT_TIMEOUT);
+        }
+        mDevice.wait(Until.findObject(By.text("CONFIDENTIAL")), 2 * SHORT_TIMEOUT);
+        UiObject2 splash = mDevice.findObject(By.text("Ok, got it"));
+        if (splash != null) {
+            splash.clickAndWait(Until.newWindow(), SHORT_TIMEOUT);
+        }
+    }
+
+    public void waitForEmailSync() {
+        // Wait up to 2 seconds for a "waiting" message to appear
+        mDevice.wait(Until.hasObject(By.text("Waiting for sync")), 2 * SHORT_TIMEOUT);
+        // Wait until any "waiting" messages are gone
+        Assert.assertTrue("'Waiting for sync' timed out",
+                mDevice.wait(Until.gone(By.text("Waiting for sync")), LONG_TIMEOUT));
+        Assert.assertTrue("'Loading' timed out",
+                mDevice.wait(Until.gone(By.text("Loading")), LONG_TIMEOUT));
+    }
+}
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/MapsJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/MapsJankTests.java
new file mode 100644
index 0000000..61329db
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/MapsJankTests.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.StaleObjectException;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import junit.framework.Assert;
+
+/**
+ * Jank test for Map
+ * click on search box to bring up ime window
+ * click on back to go back to map
+ */
+
+public class MapsJankTests extends JankTestBase {
+    private static final int SHORT_TIMEOUT = 1000;
+    private static final int LONG_TIMEOUT = 30000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.google.android.apps.maps";
+    public static final String RES_PACKAGE = "com.google.android.apps.gmm";
+    private static final String APP_NAME = "Maps";
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchMaps () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        mDevice.waitForIdle();
+        dismissCling();
+        // To infer that test is ready to be executed
+        int counter = 5;
+        while (getSearchBox() == null && counter > 0){
+            SystemClock.sleep(1000);
+            --counter;
+        }
+        Assert.assertNotNull("Failed to find 'Search'", getSearchBox());
+    }
+
+//    @JankTest(beforeTest="launchMaps", expectedFrames=EXPECTED_FRAMES)
+//    @GfxMonitor(processName=PACKAGE_NAME)
+//    public void testMapsIMEPopupForSearchEntry() {
+//    }
+
+    private UiObject2 getSearchBox(){
+        mDevice.wait(Until.findObject(By.desc("Search")), SHORT_TIMEOUT).click();
+        UiObject2 search = mDevice.wait(Until.findObject(
+            By.res(RES_PACKAGE, "search_omnibox_edit_text")), SHORT_TIMEOUT);
+        if (search == null ) {
+            search = mDevice.wait(Until.findObject(
+                    By.res(RES_PACKAGE, "search_omnibox_text_box")), SHORT_TIMEOUT);
+        }
+        Assert.assertNotNull("Search box is null", search);
+        return search;
+    }
+
+    private void dismissCling(){
+        // Accept terms
+        UiObject2 terms = mDevice.wait(Until.findObject(By.text("Accept & continue")), 5000);
+        if (terms != null) {
+            terms.click();
+        }
+        // Enable location services
+        UiObject2 location = mDevice.wait(Until.findObject(By.text("Yes, I'm in")), 5000);
+        if (location != null) {
+            location.click();
+        }
+        // Dismiss cling
+        UiObject2 cling = mDevice.wait(
+                Until.findObject(By.res(PACKAGE_NAME, "tapherehint_textbox")), 500);
+        if (cling != null) {
+            cling.click();
+        }
+        // Reset map view
+        UiObject2 resetView = mDevice.findObject(By.res(PACKAGE_NAME, "mylocation_button"));
+        if (resetView != null) {
+            resetView.click();
+            mDevice.waitForIdle(5000);
+        }
+        // dismiss yet another tutorial
+        UiObject2 tutorial = mDevice.findObject(By.res(PACKAGE_NAME, "tutorial_side_menu_got_it"));
+        if (tutorial != null) {
+            tutorial.click();
+        }
+    }
+}
diff --git a/jank/sysapp/src/com/android/sysapp/janktests/YouTubeJankTests.java b/jank/sysapp/src/com/android/sysapp/janktests/YouTubeJankTests.java
new file mode 100644
index 0000000..2cfbdae
--- /dev/null
+++ b/jank/sysapp/src/com/android/sysapp/janktests/YouTubeJankTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.sysapp.janktests;
+
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import junit.framework.Assert;
+
+/**
+ * Jank test for YouTube recommendation window fling 3 times.
+ */
+
+public class YouTubeJankTests extends JankTestBase {
+    private static final int TIMEOUT = 5000;
+    private static final int INNER_LOOP = 5;
+    private static final int EXPECTED_FRAMES = 100;
+    private static final String PACKAGE_NAME = "com.google.android.youtube";
+    private static final String APP_NAME = "YouTube";
+
+    private UiDevice mDevice;
+    private ILauncherStrategy mLauncherStrategy = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientaion", e);
+        }
+        mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    public void launchYouTube () throws UiObjectNotFoundException {
+        mLauncherStrategy.launch(APP_NAME, PACKAGE_NAME);
+        dismissCling();
+    }
+
+    // Measures jank while fling YouTube recommendation
+    @JankTest(beforeTest="launchYouTube", expectedFrames=EXPECTED_FRAMES)
+    @GfxMonitor(processName=PACKAGE_NAME)
+    public void testYouTubeRecomendationWindowFling() {
+        UiObject2 uiObject = mDevice.wait(
+                Until.findObject(By.res(PACKAGE_NAME, "pane_fragment_container")), TIMEOUT);
+        Assert.assertNotNull("Recommendation container is null", uiObject);
+        for (int i = 0; i < INNER_LOOP; i++) {
+            uiObject.scroll(Direction.DOWN, 1.0f);
+            SystemClock.sleep(100);
+            uiObject.scroll(Direction.UP, 1.0f);
+        }
+    }
+
+    private void dismissCling() {
+        // Dismiss the dogfood splash screen that might appear on first start
+        UiObject2 dialog_dismiss_btn = mDevice.wait(Until.findObject(
+                By.res(PACKAGE_NAME, "dogfood_warning_dialog_dismiss_button").text("OK")), TIMEOUT);
+        if (dialog_dismiss_btn != null) {
+            dialog_dismiss_btn.click();
+        }
+        UiObject2 welcomeSkip = mDevice.wait(
+            Until.findObject(By.res(PACKAGE_NAME, "skip_button").text("Skip")), TIMEOUT);
+        if (welcomeSkip != null) {
+            welcomeSkip.click();
+        }
+        UiObject2 musicFaster = mDevice.wait(
+            Until.findObject(By.res(PACKAGE_NAME, "text").text("Find music faster")), TIMEOUT);
+        if (musicFaster != null) {
+            UiObject2 ok = mDevice.wait(
+                    Until.findObject(By.res(PACKAGE_NAME, "ok").text("OK")), TIMEOUT);
+            Assert.assertNotNull("No 'ok' button to bypass music", ok);
+            ok.click();
+      }
+    }
+}
diff --git a/libraries/launcher-helper/Android.mk b/libraries/launcher-helper/Android.mk
new file mode 100644
index 0000000..60c499b
--- /dev/null
+++ b/libraries/launcher-helper/Android.mk
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := launcher-helper-lib
+LOCAL_JAVA_LIBRARIES := ub-uiautomator
+LOCAL_SDK_VERSION := 21
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/AospLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/AospLauncherStrategy.java
new file mode 100644
index 0000000..fe03817
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/AospLauncherStrategy.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.launcherhelper;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.widget.Button;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+/**
+ * Implementation of {@link ILauncherStrategy} to support AOSP launcher
+ */
+public class AospLauncherStrategy implements ILauncherStrategy {
+
+    private static final String LAUNCHER_PKG = "com.android.launcher";
+    private static final BySelector APPS_CONTAINER =
+            By.res(LAUNCHER_PKG, "apps_customize_pane_content");
+    private static final BySelector WORKSPACE = By.res(LAUNCHER_PKG, "workspace");
+    private static final BySelector HOTSEAT = By.res(LAUNCHER_PKG, "hotseat");
+    private UiDevice mDevice;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void open() throws UiObjectNotFoundException {
+        // if we see hotseat, assume at home screen already
+        if (!mDevice.hasObject(HOTSEAT)) {
+            mDevice.pressHome();
+            Assert.assertTrue("Failed to open launcher",
+                    mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG)), 5000));
+            mDevice.waitForIdle();
+        }
+        // remove cling if it exists
+        UiObject2 cling = mDevice.findObject(By.res(LAUNCHER_PKG, "workspace_cling"));
+        if (cling != null) {
+            cling.findObject(By.clazz(Button.class).text("OK")).click();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UiObject2 openAllApps(boolean reset) throws UiObjectNotFoundException {
+        // if we see apps container, skip the opening step, only ensure that the "Apps" tab is
+        // selected
+        if (!mDevice.hasObject(APPS_CONTAINER)) {
+            open();
+            // taps on the "apps" button at the bottom of the screen
+            mDevice.findObject(By.desc("Apps")).click();
+            // wait until hotseat disappears, so that we know that we are no longer on home screen
+            mDevice.wait(Until.gone(HOTSEAT), 2000);
+            mDevice.waitForIdle();
+        }
+        // taps on the "apps" page selector near the top of the screen
+        UiObject2 appsTab = mDevice.findObject(By.desc("Apps")
+                .clazz(TextView.class).selected(false));
+        if (appsTab != null) {
+            appsTab.click();
+        }
+        UiObject2 allAppsContainer = mDevice.findObject(APPS_CONTAINER);
+        Assert.assertNotNull("openAllApps: did not find all apps container", allAppsContainer);
+        if (reset) {
+            CommonLauncherHelper.getInstance(mDevice).scrollBackToBeginning(allAppsContainer,
+                    Direction.reverse(getAllAppsScrollDirection()));
+        }
+        return allAppsContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getAllAppsScrollDirection() {
+        return Direction.RIGHT;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UiObject2 openAllWidgets(boolean reset) throws UiObjectNotFoundException {
+        boolean needReset = true;
+        // if we see apps container, skip the opening step, only ensure that the "Widgets" tab is
+        // selected
+        if (!mDevice.hasObject(APPS_CONTAINER)) {
+            open();
+            // taps on the "apps" button at the bottom of the screen
+            mDevice.findObject(By.desc("Apps")).click();
+            // wait until hotseat disappears, so that we know that we are no longer on home screen
+            mDevice.wait(Until.gone(HOTSEAT), 2000);
+            mDevice.waitForIdle();
+        }
+        // taps on the "Widgets" page selector near the top of the screen
+        UiObject2 widgetsTab = mDevice.findObject(By.desc("Widgets")
+                .clazz(TextView.class).selected(false));
+        if (widgetsTab != null) {
+            widgetsTab.click();
+            // if we switched into widget page, then there's no need to reset, since it will go
+            // back to beginning
+            needReset = false;
+        }
+        UiObject2 allWidgetsContainer = mDevice.findObject(APPS_CONTAINER);
+        Assert.assertNotNull(
+                "openAllWidgets: did not find all widgets container", allWidgetsContainer);
+        if (reset && needReset) {
+            // only way to reset is to switch to "Apps" then back to "Widget", scroll to beginning
+            // won't work
+            mDevice.findObject(By.desc("Apps").selected(false)).click();
+            mDevice.waitForIdle();
+            mDevice.findObject(By.desc("Widgets").selected(false)).click();
+            mDevice.waitForIdle();
+        }
+        return allWidgetsContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getAllWidgetsScrollDirection() {
+        return Direction.RIGHT;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean launch(String appName, String packageName) throws UiObjectNotFoundException {
+        return CommonLauncherHelper.getInstance(mDevice).launchApp(this,
+                By.res("").clazz(TextView.class).desc(appName), packageName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUiDevice(UiDevice uiDevice) {
+        mDevice = uiDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getSupportedLauncherPackage() {
+        return LAUNCHER_PKG;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getAllAppsSelector() {
+        return APPS_CONTAINER;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getAllWidgetsSelector() {
+        return APPS_CONTAINER;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getWorkspaceSelector() {
+        return WORKSPACE;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getWorkspaceScrollDirection() {
+        return Direction.RIGHT;
+    }
+
+}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/CommonLauncherHelper.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/CommonLauncherHelper.java
new file mode 100644
index 0000000..f06ee4b
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/CommonLauncherHelper.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.launcherhelper;
+
+import android.graphics.Rect;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+/**
+ * A helper class for generic launcher interactions that can be abstracted across different types
+ * of launchers.
+ *
+ */
+public class CommonLauncherHelper {
+
+    private static final String LOG_TAG = CommonLauncherHelper.class.getSimpleName();
+    private static final int MAX_SCROLL_ATTEMPTS = 20;
+    private static final int MIN_INTERACT_SIZE = 100;
+    private static final int APP_LAUNCH_TIMEOUT = 10000;
+    private static CommonLauncherHelper sInstance;
+    private UiDevice mDevice;
+
+    private CommonLauncherHelper(UiDevice uiDevice) {
+        mDevice = uiDevice;
+    }
+
+    /**
+     * Retrieves the singleton instance of {@link CommonLauncherHelper}
+     * @param uiDevice
+     * @return
+     */
+    public static CommonLauncherHelper getInstance(UiDevice uiDevice) {
+        if (sInstance == null) {
+            sInstance = new CommonLauncherHelper(uiDevice);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Scrolls a container back to the beginning
+     * @param container
+     * @param backDirection
+     * @throws UiObjectNotFoundException
+     */
+    public void scrollBackToBeginning(UiObject2 container, Direction backDirection)
+            throws UiObjectNotFoundException {
+        scrollBackToBeginning(container, backDirection, MAX_SCROLL_ATTEMPTS);
+    }
+
+    /**
+     * Scrolls a container back to the beginning
+     * @param container
+     * @param backDirection
+     * @param maxAttempts
+     * @throws UiObjectNotFoundException
+     */
+    public void scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts)
+            throws UiObjectNotFoundException {
+        int attempts = 0;
+        while (container.fling(backDirection)) {
+            attempts++;
+            if (attempts > maxAttempts) {
+                throw new RuntimeException(
+                        "scrollBackToBeginning: exceeded max attampts: " + maxAttempts);
+            }
+        }
+    }
+
+    /**
+     * Ensures that the described widget has enough visible portion by scrolling its container if
+     * necessary
+     * @param app
+     * @param container
+     * @param dir
+     * @throws UiObjectNotFoundException
+     */
+    private void ensureIconVisible(BySelector app, UiObject2 container, Direction dir)
+            throws UiObjectNotFoundException {
+        UiObject2 appIcon = mDevice.findObject(app);
+        Rect appR = appIcon.getVisibleBounds();
+        Rect containerR = container.getVisibleBounds();
+        int size = 0;
+        int containerSize = 0;
+        if (Direction.DOWN.equals(dir) || Direction.UP.equals(dir)) {
+            size = appR.height();
+            containerSize = containerR.height();
+        } else {
+            size = appR.width();
+            containerSize = containerR.width();
+        }
+        if (size < MIN_INTERACT_SIZE) {
+            // try to figure out how much percentage of the container needs to be scrolled in order
+            // to reveal the app icon to have the MIN_INTERACT_SIZE
+            float pct = ((float)(MIN_INTERACT_SIZE - size)) / containerSize;
+            if (pct < 0.2f) {
+                pct = 0.2f;
+            }
+            container.scroll(dir, pct);
+        }
+    }
+
+    /**
+     * Triggers app launch by interacting with its launcher icon as described, optionally verify
+     * that the frontend UI has the expected app package name
+     * @param launcherStrategy
+     * @param app
+     * @param packageName
+     * @return
+     * @throws UiObjectNotFoundException
+     */
+    public boolean launchApp(ILauncherStrategy launcherStrategy, BySelector app,
+            String packageName) throws UiObjectNotFoundException {
+        return launchApp(launcherStrategy, app, packageName, MAX_SCROLL_ATTEMPTS);
+    }
+
+    /**
+     * Triggers app launch by interacting with its launcher icon as described, optionally verify
+     * that the frontend UI has the expected app package name
+     * @param launcherStrategy
+     * @param app
+     * @param packageName
+     * @param maxScrollAttempts
+     * @return
+     * @throws UiObjectNotFoundException
+     */
+    public boolean launchApp(ILauncherStrategy launcherStrategy, BySelector app,
+            String packageName, int maxScrollAttempts)
+                    throws UiObjectNotFoundException {
+        Direction dir = launcherStrategy.getAllAppsScrollDirection();
+        // attempt to find the app icon if it's not already on the screen
+        if (!mDevice.hasObject(app)) {
+            UiObject2 container = launcherStrategy.openAllApps(false);
+
+            if (!mDevice.hasObject(app)) {
+                scrollBackToBeginning(container, Direction.reverse(dir));
+                int attempts = 0;
+                while (!mDevice.hasObject(app) && container.scroll(dir, 0.8f)) {
+                    attempts++;
+                    if (attempts > maxScrollAttempts) {
+                        throw new RuntimeException(
+                                "launchApp: exceeded max attampts to locate app icon: "
+                                        + maxScrollAttempts);
+                    }
+                }
+            }
+            // HACK-ish: ensure icon has enough parts revealed for it to be clicked on
+            ensureIconVisible(app, container, dir);
+        }
+
+        if (!mDevice.findObject(app).clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT)) {
+            Log.w(LOG_TAG, "no new window detected after app launch attempt.");
+            return false;
+        }
+        mDevice.waitForIdle();
+        if (packageName != null) {
+            Log.w(LOG_TAG, String.format(
+                    "No UI element with package name %s detected.", packageName));
+            return mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT);
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/GoogleExperienceLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/GoogleExperienceLauncherStrategy.java
new file mode 100644
index 0000000..5db82a8
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/GoogleExperienceLauncherStrategy.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.launcherhelper;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Implementation of {@link ILauncherStrategy} to support Google experience launcher
+ */
+public class GoogleExperienceLauncherStrategy implements ILauncherStrategy {
+
+    private static final String LOG_TAG = GoogleExperienceLauncherStrategy.class.getSimpleName();
+    private static final String LAUNCHER_PKG = "com.google.android.googlequicksearchbox";
+    private static final BySelector APPS_CONTAINER = By.res(LAUNCHER_PKG, "all_apps_container");
+    private static final BySelector WIDGETS_CONTAINER = By.res(LAUNCHER_PKG, "widgets_list_view");
+    private static final BySelector WORKSPACE = By.res(LAUNCHER_PKG, "workspace");
+    private static final BySelector HOTSEAT = By.res(LAUNCHER_PKG, "hotseat");
+    private UiDevice mDevice;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUiDevice(UiDevice uiDevice) {
+        mDevice = uiDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void open() throws UiObjectNotFoundException {
+        // if we see hotseat, assume at home screen already
+        if (!mDevice.hasObject(HOTSEAT)) {
+            mDevice.pressHome();
+            // ensure launcher is shown
+            if (!mDevice.wait(Until.hasObject(By.res(LAUNCHER_PKG, "hotseat")), 5000)) {
+                // HACK: dump hierarchy to logcat
+                OutputStream os = new OutputStream() {
+                    StringBuilder mLineBuffer = new StringBuilder();
+                    @Override
+                    public void write(int oneByte) throws IOException {
+                        if (oneByte == '\n' || oneByte == '\r') {
+                            Log.d(LOG_TAG, mLineBuffer.toString());
+                            mLineBuffer = new StringBuilder();
+                        } else {
+                            mLineBuffer.append((char)oneByte);
+                        }
+                    }
+                    @Override
+                    public void close() throws IOException {
+                        String remainder = mLineBuffer.toString().trim();
+                        if (!remainder.isEmpty()) {
+                            Log.d(LOG_TAG, remainder);
+                        }
+                        super.close();
+                    }
+                };
+                try {
+                    mDevice.dumpWindowHierarchy(os);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "error dumping XML to logcat", ioe);
+                }
+                Assert.fail("Failed to open launcher");
+            }
+            mDevice.waitForIdle();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UiObject2 openAllApps(boolean reset) throws UiObjectNotFoundException {
+        // if we see all apps container, skip the opening step
+        if (!mDevice.hasObject(APPS_CONTAINER)) {
+            open();
+            // taps on the "apps" button at the bottom of the screen
+            mDevice.findObject(By.desc("Apps")).click();
+            // wait until hotseat disappears, so that we know that we are no longer on home screen
+            mDevice.wait(Until.gone(HOTSEAT), 2000);
+            mDevice.waitForIdle();
+        }
+        UiObject2 allAppsContainer = mDevice.wait(Until.findObject(APPS_CONTAINER), 2000);
+        Assert.assertNotNull("openAllApps: did not find all apps container", allAppsContainer);
+        if (reset) {
+            CommonLauncherHelper.getInstance(mDevice).scrollBackToBeginning(
+                    allAppsContainer, Direction.reverse(getAllAppsScrollDirection()));
+        }
+        return allAppsContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getAllAppsScrollDirection() {
+        return Direction.DOWN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UiObject2 openAllWidgets(boolean reset) throws UiObjectNotFoundException {
+        if (!mDevice.hasObject(WIDGETS_CONTAINER)) {
+            open();
+            // trigger the wallpapers/widgets/settings view
+            mDevice.pressMenu();
+            mDevice.waitForIdle();
+            mDevice.findObject(By.res(LAUNCHER_PKG, "widget_button")).click();
+        }
+        UiObject2 allWidgetsContainer = mDevice.wait(Until.findObject(WIDGETS_CONTAINER), 2000);
+        Assert.assertNotNull("openAllWidgets: did not find all widgets container",
+                allWidgetsContainer);
+        if (reset) {
+            CommonLauncherHelper.getInstance(mDevice).scrollBackToBeginning(
+                    allWidgetsContainer, Direction.reverse(getAllWidgetsScrollDirection()));
+        }
+        return allWidgetsContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getAllWidgetsScrollDirection() {
+        return Direction.DOWN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean launch(String appName, String packageName) throws UiObjectNotFoundException {
+        BySelector app = By.res(LAUNCHER_PKG, "icon").clazz(TextView.class).desc(appName);
+        return CommonLauncherHelper.getInstance(mDevice).launchApp(this, app, packageName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getSupportedLauncherPackage() {
+        return LAUNCHER_PKG;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getAllAppsSelector() {
+        return APPS_CONTAINER;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getAllWidgetsSelector() {
+        return WIDGETS_CONTAINER;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BySelector getWorkspaceSelector() {
+        return WORKSPACE;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Direction getWorkspaceScrollDirection() {
+        return Direction.RIGHT;
+    }
+}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/ILauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/ILauncherStrategy.java
new file mode 100644
index 0000000..891bbc7
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/ILauncherStrategy.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.launcherhelper;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+
+/**
+ * Defines the common use cases a launcher UI automation helper should fulfill.
+ * <p>Class will be instantiated by {@link LauncherStrategyFactory} based on current launcher
+ * package, and a {@link UiDevice} instance will be provided via {@link #setUiDevice(UiDevice)}
+ * method.
+ */
+public interface ILauncherStrategy {
+
+    /**
+     * Returns the launcher application package that this {@link ILauncherStrategy} can automate
+     * @return
+     */
+    public String getSupportedLauncherPackage();
+
+    /**
+     * Injects a {@link UiDevice} instance for UI interactions
+     * @param uiDevice
+     */
+    public void setUiDevice(UiDevice uiDevice);
+
+    /**
+     * Shows the home screen of launcher
+     * @throws UiObjectNotFoundException
+     */
+    public void open() throws UiObjectNotFoundException;
+
+    /**
+     * Opens the all apps drawer of launcher
+     * @param reset if the all apps drawer should be reset to the beginning
+     * @return {@link UiObject2} representation of the all apps drawer
+     * @throws UiObjectNotFoundException
+     */
+    public UiObject2 openAllApps(boolean reset) throws UiObjectNotFoundException;
+
+    /**
+     * Returns a {@link BySelector} describing the all apps drawer
+     * @return
+     */
+    public BySelector getAllAppsSelector();
+
+    /**
+     * Retrieves the all apps drawer forward scroll direction as implemented by the launcher
+     * @return
+     */
+    public Direction getAllAppsScrollDirection();
+
+    /**
+     * Opens the all widgets drawer of launcher
+     * @param reset if the all widgets drawer should be reset to the beginning
+     * @return {@link UiObject2} representation of the all widgets drawer
+     * @throws UiObjectNotFoundException
+     */
+    public UiObject2 openAllWidgets(boolean reset) throws UiObjectNotFoundException;
+
+    /**
+     * Returns a {@link BySelector} describing the all widgets drawer
+     * @return
+     */
+    public BySelector getAllWidgetsSelector();
+
+    /**
+     * Retrieves the all widgets drawer forward scroll direction as implemented by the launcher
+     * @return
+     */
+    public Direction getAllWidgetsScrollDirection();
+
+    /**
+     * Returns a {@link BySelector} describing the home screen workspace
+     * @return
+     */
+    public BySelector getWorkspaceSelector();
+
+    /**
+     * Retrieves the home screen workspace forward scroll direction as implemented by the launcher
+     * @return
+     */
+    public Direction getWorkspaceScrollDirection();
+
+    /**
+     * Launch the named application
+     * @param appName the name of the application to launch as shown in launcher
+     * @param packageName the expected package name to verify that the application has been launched
+     *                    into foreground. If <code>null</code> is provided, no verification is
+     *                    performed.
+     * @return <code>true</code> if application is verified to be in foreground after launch, or the
+     *   verification is skipped; <code>false</code> otherwise.
+     * @throws UiObjectNotFoundException
+     */
+    public boolean launch(String appName, String packageName) throws UiObjectNotFoundException;
+}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java
new file mode 100644
index 0000000..880638b
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.launcherhelper;
+
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Factory class that handles registering of {@link ILauncherStrategy} and providing a suitable
+ * launcher helper based on current launcher available
+ */
+public class LauncherStrategyFactory {
+
+    private static final String LOG_TAG = LauncherStrategyFactory.class.getSimpleName();
+    private static LauncherStrategyFactory sInstance;
+    private UiDevice mUiDevice;
+    private Map<String, ILauncherStrategy> mInstanceMap;
+    private Set<Class <? extends ILauncherStrategy>> mKnownLauncherStrategies;
+
+    private LauncherStrategyFactory(UiDevice uiDevice) {
+        mUiDevice = uiDevice;
+        mInstanceMap = new HashMap<>();
+        mKnownLauncherStrategies = new HashSet<>();
+        registerLauncherStrategy(AospLauncherStrategy.class);
+        registerLauncherStrategy(GoogleExperienceLauncherStrategy.class);
+    }
+
+    /**
+     * Retrieves an instance of the {@link LauncherStrategyFactory}
+     * @param context
+     * @return
+     */
+    public static LauncherStrategyFactory getInstance(UiDevice uiDevice) {
+        if (sInstance == null) {
+            sInstance = new LauncherStrategyFactory(uiDevice);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Registers an {@link ILauncherStrategy}.
+     * <p>Note that the registration is by class so that the caller does not need to instantiate
+     * multiple instances of the same class.
+     * @param launcherStrategy
+     */
+    public void registerLauncherStrategy(Class<? extends ILauncherStrategy> launcherStrategy) {
+        // ignore repeated registering attempts
+        if (!mKnownLauncherStrategies.contains(launcherStrategy)) {
+            try {
+                ILauncherStrategy strategy = launcherStrategy.newInstance();
+                strategy.setUiDevice(mUiDevice);
+                mInstanceMap.put(strategy.getSupportedLauncherPackage(), strategy);
+            } catch (InstantiationException | IllegalAccessException e) {
+                Log.e(LOG_TAG, "exception while creating instance: "
+                        + launcherStrategy.getCanonicalName());
+            }
+        }
+    }
+
+    /**
+     * Retrieves a {@link ILauncherStrategy} that supports the current default launcher
+     * <p>
+     * {@link ILauncherStrategy} maybe registered via
+     * {@link LauncherStrategyRegistry#registerLauncherStrategy(String, Class)} by identifying the
+     * launcher package name supported
+     * @return
+     */
+    public ILauncherStrategy getLauncherStrategy() {
+        String launcherPkg = mUiDevice.getLauncherPackageName();
+        return mInstanceMap.get(launcherPkg);
+    }
+}