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);
+ }
+}