From 6d02c7a0337d37e5b1c596a4acec0c292a9b7f93 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 19 May 2016 12:15:39 -0700 Subject: Adding some UI tests > Launcher app from all-apps > Drag icon to all-apps and launch it > Add widget from widget tray Change-Id: I6bd6128a7b560a23a887d1fb40bfcda25b9b02e7 --- .../src/com/android/launcher3/BindWidgetTest.java | 101 +------- .../com/android/launcher3/QuickAddWidgetTest.java | 88 ------- .../android/launcher3/RotationPreferenceTest.java | 90 ------- .../com/android/launcher3/ui/AddWidgetTest.java | 64 +++++ .../android/launcher3/ui/AllAppsAppLaunchTest.java | 52 ++++ .../launcher3/ui/AllAppsIconToHomeTest.java | 57 +++++ .../ui/LauncherInstrumentationTestCase.java | 266 +++++++++++++++++++++ .../launcher3/ui/RotationPreferenceTest.java | 77 ++++++ .../src/com/android/launcher3/util/Condition.java | 54 +++++ tests/src/com/android/launcher3/util/Wait.java | 30 +++ 10 files changed, 605 insertions(+), 274 deletions(-) delete mode 100644 tests/src/com/android/launcher3/QuickAddWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/RotationPreferenceTest.java create mode 100644 tests/src/com/android/launcher3/ui/AddWidgetTest.java create mode 100644 tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java create mode 100644 tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java create mode 100644 tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java create mode 100644 tests/src/com/android/launcher3/ui/RotationPreferenceTest.java create mode 100644 tests/src/com/android/launcher3/util/Condition.java create mode 100644 tests/src/com/android/launcher3/util/Wait.java (limited to 'tests/src') diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java index 34b117430..81cb8b56b 100644 --- a/tests/src/com/android/launcher3/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/BindWidgetTest.java @@ -1,38 +1,30 @@ package com.android.launcher3; import android.annotation.TargetApi; -import android.app.SearchManager; import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Build; import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiSelector; -import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.ui.LauncherInstrumentationTestCase; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; -import java.io.FileInputStream; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; /** * Tests for bind widget flow. @@ -41,12 +33,8 @@ import java.util.concurrent.atomic.AtomicReference; */ @LargeTest @TargetApi(Build.VERSION_CODES.LOLLIPOP) -public class BindWidgetTest extends InstrumentationTestCase { +public class BindWidgetTest extends LauncherInstrumentationTestCase { - private static final long DEFAULT_TIMEOUT = 6000; - - private UiDevice mDevice; - private Context mTargetContext; private ContentResolver mResolver; private AppWidgetManagerCompat mWidgetManager; @@ -59,23 +47,9 @@ public class BindWidgetTest extends InstrumentationTestCase { protected void setUp() throws Exception { super.setUp(); - mDevice = UiDevice.getInstance(getInstrumentation()); - mTargetContext = getInstrumentation().getTargetContext(); mResolver = mTargetContext.getContentResolver(); mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext); - - // Check bind widget permission - String pkg = mTargetContext.getPackageName(); - if (mTargetContext.getPackageManager().checkPermission( - pkg, android.Manifest.permission.BIND_APPWIDGET) - != PackageManager.PERMISSION_GRANTED) { - ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand( - "appwidget grantbind --package " + pkg); - // Read the input stream fully. - FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); - while (fis.read() != -1); - fis.close(); - } + grantWidgetPermission(); // Clear all existing data LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); @@ -264,61 +238,14 @@ public class BindWidgetTest extends InstrumentationTestCase { throw new IllegalArgumentException(t); } // Launch the home activity - getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setPackage(mTargetContext.getPackageName()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - + startLauncher(); // Verify UI UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) .className(widgetClass); if (desc != null) { selector = selector.description(desc); } - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_TIMEOUT)); - } - - /** - * Finds a widget provider which can fit on the home screen. - * @param hasConfigureScreen if true, a provider with a config screen is returned. - */ - private LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { - LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable() { - @Override - public LauncherAppWidgetProviderInfo call() throws Exception { - InvariantDeviceProfile idv = - LauncherAppState.getInstance().getInvariantDeviceProfile(); - - ComponentName searchComponent = ((SearchManager) mTargetContext - .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity(); - String searchPackage = searchComponent == null - ? null : searchComponent.getPackageName(); - - for (AppWidgetProviderInfo info : - AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) { - if ((info.configure != null) ^ hasConfigureScreen) { - continue; - } - // Exclude the widgets in search package, as Launcher already binds them in - // QSB, so they can cause conflicts. - if (info.provider.getPackageName().equals(searchPackage)) { - continue; - } - LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo - .fromProviderInfo(mTargetContext, info); - if (widgetInfo.minSpanX >= idv.numColumns - || widgetInfo.minSpanY >= idv.numRows) { - continue; - } - return widgetInfo; - } - return null; - } - }); - if (info == null) { - throw new IllegalArgumentException("No valid widget provider"); - } - return info; + assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); } /** @@ -397,24 +324,6 @@ public class BindWidgetTest extends InstrumentationTestCase { return item; } - /** - * Runs the callback on the UI thread and returns the result. - */ - private T getOnUiThread(final Callable callback) { - final AtomicReference result = new AtomicReference<>(null); - try { - runTestOnUiThread(new Runnable() { - @Override - public void run() { - try { - result.set(callback.call()); - } catch (Exception e) { } - } - }); - } catch (Throwable t) { } - return result.get(); - } - /** * Blocks the current thread until all the jobs in the main worker thread are complete. */ diff --git a/tests/src/com/android/launcher3/QuickAddWidgetTest.java b/tests/src/com/android/launcher3/QuickAddWidgetTest.java deleted file mode 100644 index 007829435..000000000 --- a/tests/src/com/android/launcher3/QuickAddWidgetTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.android.launcher3; - -import android.content.Intent; -import android.graphics.Point; -import android.os.SystemClock; -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.Until; -import android.test.InstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.view.MotionEvent; -import android.view.ViewConfiguration; - -import java.util.List; - -/** - * Add an arbitrary widget from the widget picker very quickly to test potential race conditions. - */ -@LargeTest -public class QuickAddWidgetTest extends InstrumentationTestCase { - // Disabled because it's flaky and not particularly useful. But this class could still be useful - // as an example if we want other UI tests in the future. - private static final boolean DISABLED = true; - - private UiDevice mDevice; - private String mTargetPackage; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mDevice = UiDevice.getInstance(getInstrumentation()); - - // Set Launcher3 as home. - mTargetPackage = getInstrumentation().getTargetContext().getPackageName(); - Intent homeIntent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setPackage(mTargetPackage) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getInstrumentation().getContext().startActivity(homeIntent); - mDevice.wait(Until.hasObject(By.pkg(mTargetPackage).depth(0)), 3000); - } - - public void testAddWidgetQuickly() throws Exception { - if (DISABLED) return; - mDevice.pressMenu(); // Enter overview mode. - mDevice.wait(Until.findObject(By.text("Widgets")), 3000).click(); - UiObject2 calendarWidget = getWidgetByName("Clock"); - Point center = calendarWidget.getVisibleCenter(); - // Touch widget just long enough to pick it up (longPressTimeout), then let go immediately. - getInstrumentation().sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, center.x, center.y, 0)); - Thread.sleep(ViewConfiguration.getLongPressTimeout() + 50); - getInstrumentation().sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, center.x, center.y, 0)); - - assertTrue("Drag was never started", isOnHomescreen()); - } - - private UiObject2 getWidgetByName(String name) { - UiObject2 widgetsList = mDevice.wait(Until.findObject(By.res(mTargetPackage, - "widgets_list_view")), 3000); - do { - UiObject2 widget = getVisibleWidgetByName(name); - if (widget != null) { - return widget; - } - } while (widgetsList.scroll(Direction.DOWN, 1f)); - return getVisibleWidgetByName(name); - } - - private UiObject2 getVisibleWidgetByName(String name) { - List visibleWidgets = mDevice.wait(Until.findObjects(By.clazz( - "android.widget.LinearLayout")), 3000); - for (UiObject2 widget : visibleWidgets) { - if (widget.hasObject(By.text(name))) { - return widget; - } - } - return null; - } - - private boolean isOnHomescreen() { - return mDevice.wait(Until.hasObject(By.res(mTargetPackage, "hotseat")), 3000); - } -} diff --git a/tests/src/com/android/launcher3/RotationPreferenceTest.java b/tests/src/com/android/launcher3/RotationPreferenceTest.java deleted file mode 100644 index 7259d35c2..000000000 --- a/tests/src/com/android/launcher3/RotationPreferenceTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.android.launcher3; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Rect; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject; -import android.support.test.uiautomator.UiSelector; -import android.test.InstrumentationTestCase; -import android.test.suitebuilder.annotation.MediumTest; - -/** - * Test for auto rotate preference. - */ -@MediumTest -public class RotationPreferenceTest extends InstrumentationTestCase { - - private UiDevice mDevice; - private Context mTargetContext; - private String mTargetPackage; - - private SharedPreferences mPrefs; - private boolean mOriginalRotationValue; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mDevice = UiDevice.getInstance(getInstrumentation()); - mTargetContext = getInstrumentation().getTargetContext(); - mTargetPackage = mTargetContext.getPackageName(); - mPrefs = Utilities.getPrefs(mTargetContext); - mOriginalRotationValue = mPrefs.getBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, false); - } - - @Override - protected void tearDown() throws Exception { - setRotationEnabled(mOriginalRotationValue); - super.tearDown(); - } - - public void testRotation_disabled() throws Exception { - if (mTargetContext.getResources().getBoolean(R.bool.allow_rotation)) { - // This is a tablet. The test is only valid to mobile devices. - return; - } - - setRotationEnabled(false); - mDevice.setOrientationRight(); - goToLauncher(); - - Rect hotseat = getHotseatBounds(); - assertTrue(hotseat.width() > hotseat.height()); - } - - public void testRotation_enabled() throws Exception { - if (mTargetContext.getResources().getBoolean(R.bool.allow_rotation)) { - // This is a tablet. The test is only valid to mobile devices. - return; - } - - setRotationEnabled(true); - mDevice.setOrientationRight(); - goToLauncher(); - - Rect hotseat = getHotseatBounds(); - assertTrue(hotseat.width() < hotseat.height()); - } - - private void goToLauncher() { - Intent homeIntent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setPackage(mTargetPackage) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getInstrumentation().getContext().startActivity(homeIntent); - mDevice.findObject(new UiSelector().packageName(mTargetPackage)).waitForExists(6000); - } - - private void setRotationEnabled(boolean enabled) { - mPrefs.edit().putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, enabled).commit(); - } - - private Rect getHotseatBounds() throws Exception { - UiObject hotseat = mDevice.findObject( - new UiSelector().resourceId(mTargetPackage + ":id/hotseat")); - hotseat.waitForExists(6000); - return hotseat.getVisibleBounds(); - } -} diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/AddWidgetTest.java new file mode 100644 index 000000000..95d9289dd --- /dev/null +++ b/tests/src/com/android/launcher3/ui/AddWidgetTest.java @@ -0,0 +1,64 @@ +package com.android.launcher3.ui; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiObject2; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.Wait; +import com.android.launcher3.widget.WidgetCell; + +/** + * Test to add widget from widget tray + */ +@LargeTest +public class AddWidgetTest extends LauncherInstrumentationTestCase { + + private LauncherAppWidgetProviderInfo widgetInfo; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + widgetInfo = findWidgetProvider(false /* hasConfigureScreen */); + } + + public void testDragIcon_portrait() throws Throwable { + lockRotation(true); + performTest(); + } + + public void testDragIcon_landscape() throws Throwable { + lockRotation(false); + performTest(); + } + + private void performTest() throws Throwable { + clearHomescreen(); + Launcher launcher = startLauncher(); + + // Open all apps and wait for load complete. + final UiObject2 widgetContainer = openWidgetsTray(); + assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT)); + + // Drag widget to homescreen + UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class) + .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager())))); + dragToWorkspace(widget); + + assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).providerName.equals(widgetInfo.provider); + } + })); + } +} diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java new file mode 100644 index 000000000..abe6b9591 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java @@ -0,0 +1,52 @@ +package com.android.launcher3.ui; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.launcher3.compat.LauncherActivityInfoCompat; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.Wait; + +/** + * Test for verifying apps is launched from all-apps + */ +@LargeTest +public class AllAppsAppLaunchTest extends LauncherInstrumentationTestCase { + + private LauncherActivityInfoCompat mSettingsApp; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) + .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0); + } + + public void testAppLauncher_portrait() throws Exception { + lockRotation(true); + performTest(); + } + + public void testAppLauncher_landscape() throws Exception { + lockRotation(false); + performTest(); + } + + private void performTest() throws Exception { + startLauncher(); + + // Open all apps and wait for load complete + final UiObject2 appsContainer = openAllApps(); + assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT)); + + // Open settings app and verify app launched + scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())).click(); + assertTrue(mDevice.wait(Until.hasObject(By.pkg( + mSettingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT)); + } +} diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java new file mode 100644 index 000000000..56fc90ab1 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java @@ -0,0 +1,57 @@ +package com.android.launcher3.ui; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.launcher3.compat.LauncherActivityInfoCompat; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.Wait; + +/** + * Test for dragging an icon from all-apps to homescreen. + */ +@LargeTest +public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase { + + private LauncherActivityInfoCompat mSettingsApp; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) + .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0); + } + + public void testDragIcon_portrait() throws Throwable { + lockRotation(true); + performTest(); + } + + public void testDragIcon_landscape() throws Throwable { + lockRotation(false); + performTest(); + } + + private void performTest() throws Throwable { + clearHomescreen(); + startLauncher(); + + // Open all apps and wait for load complete. + final UiObject2 appsContainer = openAllApps(); + assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT)); + + // Drag icon to homescreen. + UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())); + dragToWorkspace(icon); + + // Verify that the icon works on homescreen. + mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click(); + assertTrue(mDevice.wait(Until.hasObject(By.pkg( + mSettingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT)); + } +} diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java new file mode 100644 index 000000000..a59f0ffc9 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java @@ -0,0 +1,266 @@ +package com.android.launcher3.ui; + +import android.app.SearchManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Point; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +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.Until; +import android.test.InstrumentationTestCase; +import android.view.MotionEvent; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.LauncherClings; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.util.ManagedProfileHeuristic; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Locale; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Base class for all instrumentation tests providing various utility methods. + */ +public class LauncherInstrumentationTestCase extends InstrumentationTestCase { + + public static final long DEFAULT_UI_TIMEOUT = 3000; + + protected UiDevice mDevice; + protected Context mTargetContext; + protected String mTargetPackage; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mDevice = UiDevice.getInstance(getInstrumentation()); + mTargetContext = getInstrumentation().getTargetContext(); + mTargetPackage = mTargetContext.getPackageName(); + } + + protected void lockRotation(boolean naturalOrientation) throws RemoteException { + Utilities.getPrefs(mTargetContext) + .edit() + .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation) + .commit(); + + if (naturalOrientation) { + mDevice.setOrientationNatural(); + } else { + mDevice.setOrientationRight(); + } + } + + /** + * Starts the launcher activity in the target package and returns the Launcher instance. + */ + protected Launcher startLauncher() { + Intent homeIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setPackage(mTargetPackage) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return (Launcher) getInstrumentation().startActivitySync(homeIntent); + } + + /** + * Grants the launcher permission to bind widgets. + */ + protected void grantWidgetPermission() throws IOException { + // Check bind widget permission + if (mTargetContext.getPackageManager().checkPermission( + mTargetPackage, android.Manifest.permission.BIND_APPWIDGET) + != PackageManager.PERMISSION_GRANTED) { + ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand( + "appwidget grantbind --package " + mTargetPackage); + // Read the input stream fully. + FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + while (fis.read() != -1); + fis.close(); + } + } + + /** + * Opens all apps and returns the recycler view + */ + protected UiObject2 openAllApps() { + mDevice.wait(Until.findObject( + By.desc(mTargetContext.getString(R.string.all_apps_button_label))), DEFAULT_UI_TIMEOUT).click(); + return findViewById(R.id.apps_list_view); + } + + /** + * Opens widget tray and returns the recycler view. + */ + protected UiObject2 openWidgetsTray() { + mDevice.pressMenu(); // Enter overview mode. + mDevice.wait(Until.findObject( + By.text(mTargetContext.getString(R.string.widget_button_text) + .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click(); + return findViewById(R.id.widgets_list_view); + } + + /** + * Scrolls the {@param container} until it finds an object matching {@param condition}. + * @return the matching object. + */ + protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { + do { + UiObject2 widget = container.findObject(condition); + if (widget != null) { + return widget; + } + } while (container.scroll(Direction.DOWN, 1f)); + return container.findObject(condition); + } + + /** + * Drags an icon to the center of homescreen. + */ + protected void dragToWorkspace(UiObject2 icon) { + Point center = icon.getVisibleCenter(); + + // Action Down + sendPointer(MotionEvent.ACTION_DOWN, center); + + // Wait until "Remove/Delete target is visible + assertNotNull(findViewById(R.id.delete_target_text)); + + Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter(); + + // Move to center + while(!moveLocation.equals(center)) { + center.x = getNextMoveValue(moveLocation.x, center.x); + center.y = getNextMoveValue(moveLocation.y, center.y); + sendPointer(MotionEvent.ACTION_MOVE, center); + } + sendPointer(MotionEvent.ACTION_UP, center); + + // Wait until remove target is gone. + mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT); + } + + private int getNextMoveValue(int targetValue, int oldValue) { + if (targetValue - oldValue > 10) { + return oldValue + 10; + } else if (targetValue - oldValue < -10) { + return oldValue - 10; + } else { + return targetValue; + } + } + + private void sendPointer(int action, Point point) { + MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), action, point.x, point.y, 0); + getInstrumentation().sendPointerSync(event); + event.recycle(); + } + + /** + * Removes all icons from homescreen and hotseat. + */ + public void clearHomescreen() throws Throwable { + LauncherSettings.Settings.call(mTargetContext.getContentResolver(), + LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); + LauncherSettings.Settings.call(mTargetContext.getContentResolver(), + LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); + LauncherClings.markFirstRunClingDismissed(mTargetContext); + ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); + + runTestOnUiThread(new Runnable() { + @Override + public void run() { + // Reset the loader state + LauncherAppState.getInstance().getModel().resetLoadedState(true, true); + } + }); + } + + /** + * Runs the callback on the UI thread and returns the result. + */ + protected T getOnUiThread(final Callable callback) { + final AtomicReference result = new AtomicReference<>(null); + try { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + try { + result.set(callback.call()); + } catch (Exception e) { } + } + }); + } catch (Throwable t) { } + return result.get(); + } + + /** + * Finds a widget provider which can fit on the home screen. + * @param hasConfigureScreen if true, a provider with a config screen is returned. + */ + protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { + LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable() { + @Override + public LauncherAppWidgetProviderInfo call() throws Exception { + InvariantDeviceProfile idv = + LauncherAppState.getInstance().getInvariantDeviceProfile(); + + ComponentName searchComponent = ((SearchManager) mTargetContext + .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity(); + String searchPackage = searchComponent == null + ? null : searchComponent.getPackageName(); + + for (AppWidgetProviderInfo info : + AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) { + if ((info.configure != null) ^ hasConfigureScreen) { + continue; + } + // Exclude the widgets in search package, as Launcher already binds them in + // QSB, so they can cause conflicts. + if (info.provider.getPackageName().equals(searchPackage)) { + continue; + } + LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo + .fromProviderInfo(mTargetContext, info); + if (widgetInfo.minSpanX >= idv.numColumns + || widgetInfo.minSpanY >= idv.numRows) { + continue; + } + return widgetInfo; + } + return null; + } + }); + if (info == null) { + throw new IllegalArgumentException("No valid widget provider"); + } + return info; + } + + protected UiObject2 findViewById(int id) { + return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT); + } + + protected BySelector getSelectorForId(int id) { + String name = mTargetContext.getResources().getResourceEntryName(id); + return By.res(mTargetPackage, name); + } +} diff --git a/tests/src/com/android/launcher3/ui/RotationPreferenceTest.java b/tests/src/com/android/launcher3/ui/RotationPreferenceTest.java new file mode 100644 index 000000000..e84ad047f --- /dev/null +++ b/tests/src/com/android/launcher3/ui/RotationPreferenceTest.java @@ -0,0 +1,77 @@ +package com.android.launcher3.ui; + +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject; +import android.support.test.uiautomator.UiSelector; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; + +/** + * Test for auto rotate preference. + */ +@MediumTest +public class RotationPreferenceTest extends LauncherInstrumentationTestCase { + + private SharedPreferences mPrefs; + private boolean mOriginalRotationValue; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mDevice = UiDevice.getInstance(getInstrumentation()); + mTargetContext = getInstrumentation().getTargetContext(); + mTargetPackage = mTargetContext.getPackageName(); + mPrefs = Utilities.getPrefs(mTargetContext); + mOriginalRotationValue = mPrefs.getBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, false); + } + + @Override + protected void tearDown() throws Exception { + setRotationEnabled(mOriginalRotationValue); + super.tearDown(); + } + + public void testRotation_disabled() throws Exception { + if (mTargetContext.getResources().getBoolean(R.bool.allow_rotation)) { + // This is a tablet. The test is only valid to mobile devices. + return; + } + + setRotationEnabled(false); + mDevice.setOrientationRight(); + startLauncher(); + + Rect hotseat = getHotseatBounds(); + assertTrue(hotseat.width() > hotseat.height()); + } + + public void testRotation_enabled() throws Exception { + if (mTargetContext.getResources().getBoolean(R.bool.allow_rotation)) { + // This is a tablet. The test is only valid to mobile devices. + return; + } + + setRotationEnabled(true); + mDevice.setOrientationRight(); + startLauncher(); + + Rect hotseat = getHotseatBounds(); + assertTrue(hotseat.width() < hotseat.height()); + } + + private void setRotationEnabled(boolean enabled) { + mPrefs.edit().putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, enabled).commit(); + } + + private Rect getHotseatBounds() throws Exception { + UiObject hotseat = mDevice.findObject( + new UiSelector().resourceId(mTargetPackage + ":id/hotseat")); + hotseat.waitForExists(6000); + return hotseat.getVisibleBounds(); + } +} diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java new file mode 100644 index 000000000..e9ee67cdb --- /dev/null +++ b/tests/src/com/android/launcher3/util/Condition.java @@ -0,0 +1,54 @@ +package com.android.launcher3.util; + +import android.support.test.uiautomator.UiObject2; + +import com.android.launcher3.MainThreadExecutor; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class Condition { + + public abstract boolean isTrue() throws Throwable; + + /** + * Converts the condition to be run on UI thread. + */ + public static Condition runOnUiThread(final Condition condition) { + final MainThreadExecutor executor = new MainThreadExecutor(); + return new Condition() { + @Override + public boolean isTrue() throws Throwable { + final AtomicBoolean value = new AtomicBoolean(false); + final Throwable[] exceptions = new Throwable[1]; + final CountDownLatch latch = new CountDownLatch(1); + executor.execute(new Runnable() { + @Override + public void run() { + try { + value.set(condition.isTrue()); + } catch (Throwable e) { + exceptions[0] = e; + } + + } + }); + latch.await(1, TimeUnit.SECONDS); + if (exceptions[0] != null) { + throw exceptions[0]; + } + return value.get(); + } + }; + } + + public static Condition minChildCount(final UiObject2 obj, final int childCount) { + return new Condition() { + @Override + public boolean isTrue() { + return obj.getChildCount() >= childCount; + } + }; + } +} diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java new file mode 100644 index 000000000..02a19137e --- /dev/null +++ b/tests/src/com/android/launcher3/util/Wait.java @@ -0,0 +1,30 @@ +package com.android.launcher3.util; + +import android.os.SystemClock; + +/** + * A utility class for waiting for a condition to be true. + */ +public class Wait { + + private static final long DEFAULT_SLEEP_MS = 200; + + public static boolean atMost(Condition condition, long timeout) { + return atMost(condition, timeout, DEFAULT_SLEEP_MS); + } + + public static boolean atMost(Condition condition, long timeout, long sleepMillis) { + long endTime = SystemClock.uptimeMillis() + timeout; + while (SystemClock.uptimeMillis() < endTime) { + try { + if (condition.isTrue()) { + return true; + } + } catch (Throwable t) { + // Ignore + } + SystemClock.sleep(sleepMillis); + } + return false; + } +} -- cgit v1.2.3