diff options
Diffstat (limited to 'tests/src/com/android/launcher3/ui')
-rw-r--r-- | tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java | 8 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java | 11 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java | 215 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java | 69 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java | 75 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java | 198 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java (renamed from tests/src/com/android/launcher3/ui/AddWidgetTest.java) | 22 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java | 349 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java | 229 |
9 files changed, 1094 insertions, 82 deletions
diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java index abe6b9591..0ced7cf33 100644 --- a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java +++ b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java @@ -1,13 +1,13 @@ package com.android.launcher3.ui; +import android.content.pm.LauncherActivityInfo; +import android.os.Process; 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; @@ -17,14 +17,14 @@ import com.android.launcher3.util.Wait; @LargeTest public class AllAppsAppLaunchTest extends LauncherInstrumentationTestCase { - private LauncherActivityInfoCompat mSettingsApp; + private LauncherActivityInfo mSettingsApp; @Override protected void setUp() throws Exception { super.setUp(); mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) - .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0); + .getActivityList("com.android.settings", Process.myUserHandle()).get(0); } public void testAppLauncher_portrait() throws Exception { diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java index 56fc90ab1..936175087 100644 --- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java +++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java @@ -1,13 +1,13 @@ package com.android.launcher3.ui; +import android.content.pm.LauncherActivityInfo; +import android.os.Process; 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; @@ -17,14 +17,15 @@ import com.android.launcher3.util.Wait; @LargeTest public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase { - private LauncherActivityInfoCompat mSettingsApp; + private LauncherActivityInfo mSettingsApp; @Override protected void setUp() throws Exception { super.setUp(); + setDefaultLauncher(); mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) - .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0); + .getActivityList("com.android.settings", Process.myUserHandle()).get(0); } public void testDragIcon_portrait() throws Throwable { @@ -47,7 +48,7 @@ public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase { // Drag icon to homescreen. UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())); - dragToWorkspace(icon); + dragToWorkspace(icon, true); // Verify that the icon works on homescreen. mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click(); diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java index e858d17f3..47b43f530 100644 --- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java +++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java @@ -1,13 +1,31 @@ +/* + * Copyright (C) 2017 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.launcher3.ui; -import android.app.SearchManager; -import android.appwidget.AppWidgetProviderInfo; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Point; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.support.test.uiautomator.By; @@ -19,30 +37,39 @@ 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.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.testcomponent.AppWidgetNoConfig; +import com.android.launcher3.testcomponent.AppWidgetWithConfig; import com.android.launcher3.util.ManagedProfileHeuristic; +import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.Locale; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Base class for all instrumentation tests providing various utility methods. */ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { + public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; + public static final long DEFAULT_UI_TIMEOUT = 3000; + public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; protected UiDevice mDevice; protected Context mTargetContext; @@ -74,11 +101,14 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { * Starts the launcher activity in the target package and returns the Launcher instance. */ protected Launcher startLauncher() { - Intent homeIntent = new Intent(Intent.ACTION_MAIN) + return (Launcher) getInstrumentation().startActivitySync(getHomeIntent()); + } + + protected Intent getHomeIntent() { + return new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setPackage(mTargetPackage) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return (Launcher) getInstrumentation().startActivitySync(homeIntent); } /** @@ -89,16 +119,31 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { 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(); + runShellCommand("appwidget grantbind --package " + mTargetPackage); } } /** + * Sets the target launcher as default launcher. + */ + protected void setDefaultLauncher() throws IOException { + ActivityInfo launcher = mTargetContext.getPackageManager() + .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo; + runShellCommand("cmd package set-home-activity " + + new ComponentName(launcher.packageName, launcher.name).flattenToString()); + } + + protected void runShellCommand(String command) throws IOException { + ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation() + .executeShellCommand(command); + + // 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() { @@ -141,29 +186,52 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { /** * Drags an icon to the center of homescreen. */ - protected void dragToWorkspace(UiObject2 icon) { + protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) { Point center = icon.getVisibleCenter(); // Action Down sendPointer(MotionEvent.ACTION_DOWN, center); - // Wait until "Remove/Delete target is visible + UiObject2 dragLayer = findViewById(R.id.drag_layer); + + if (expectedToShowShortcuts) { + // Make sure shortcuts show up, and then move a bit to hide them. + assertNotNull(findViewById(R.id.deep_shortcuts_container)); + + Point moveLocation = new Point(center); + int distanceToMove = mTargetContext.getResources().getDimensionPixelSize( + R.dimen.deep_shortcuts_start_drag_threshold) + 50; + if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) { + moveLocation.y -= distanceToMove; + } else { + moveLocation.y += distanceToMove; + } + movePointer(center, moveLocation); + + assertNull(findViewById(R.id.deep_shortcuts_container)); + } + + // Wait until Remove/Delete target is visible assertNotNull(findViewById(R.id.delete_target_text)); - Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter(); + Point moveLocation = dragLayer.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); - } + movePointer(center, moveLocation); 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 void movePointer(Point from, Point to) { + while(!from.equals(to)) { + from.x = getNextMoveValue(to.x, from.x); + from.y = getNextMoveValue(to.y, from.y); + sendPointer(MotionEvent.ACTION_MOVE, from); + } + } + private int getNextMoveValue(int targetValue, int oldValue) { if (targetValue - oldValue > 10) { return oldValue + 10; @@ -174,7 +242,7 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { } } - private void sendPointer(int action, Point point) { + protected void sendPointer(int action, Point point) { MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), action, point.x, point.y, 0); getInstrumentation().sendPointerSync(event); @@ -189,34 +257,32 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { 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); - } - }); + resetLoaderState(); } - /** - * Runs the callback on the UI thread and returns the result. - */ - protected <T> T getOnUiThread(final Callable<T> callback) { - final AtomicReference<T> result = new AtomicReference<>(null); + protected void resetLoaderState() { try { runTestOnUiThread(new Runnable() { @Override public void run() { - try { - result.set(callback.call()); - } catch (Exception e) { } + ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); + LauncherAppState.getInstance(mTargetContext).getModel().forceReload(); } }); - } catch (Throwable t) { } - return result.get(); + } catch (Throwable t) { + throw new IllegalArgumentException(t); + } + } + + /** + * Runs the callback on the UI thread and returns the result. + */ + protected <T> T getOnUiThread(final Callable<T> callback) { + try { + return new MainThreadExecutor().submit(callback).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } } /** @@ -224,36 +290,14 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { * @param hasConfigureScreen if true, a provider with a config screen is returned. */ protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) { - LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { + LauncherAppWidgetProviderInfo info = + getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() { @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; + ComponentName cn = new ComponentName(getInstrumentation().getContext(), + hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class); + return AppWidgetManagerCompat.getInstance(mTargetContext) + .findProvider(cn, Process.myUserHandle()); } }); if (info == null) { @@ -270,4 +314,35 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { String name = mTargetContext.getResources().getResourceEntryName(id); return By.res(mTargetPackage, name); } + + + /** + * Broadcast receiver which blocks until the result is received. + */ + public class BlockingBroadcastReceiver extends BroadcastReceiver { + + private final CountDownLatch latch = new CountDownLatch(1); + private Intent mIntent; + + public BlockingBroadcastReceiver(String action) { + mTargetContext.registerReceiver(this, new IntentFilter(action)); + } + + @Override + public void onReceive(Context context, Intent intent) { + mIntent = intent; + latch.countDown(); + } + + public Intent blockingGetIntent() throws InterruptedException { + latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS); + mTargetContext.unregisterReceiver(this); + return mIntent; + } + + public Intent blockingGetExtraIntent() throws InterruptedException { + Intent intent = blockingGetIntent(); + return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); + } + } } diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java new file mode 100644 index 000000000..3a0b6132c --- /dev/null +++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java @@ -0,0 +1,69 @@ +package com.android.launcher3.ui; + +import android.content.pm.LauncherActivityInfo; +import android.graphics.Point; +import android.os.Process; +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 android.view.MotionEvent; + +import com.android.launcher3.R; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.Wait; + +/** + * Test for verifying that shortcuts are shown and can be launched after long pressing an app + */ +@LargeTest +public class ShortcutsLaunchTest extends LauncherInstrumentationTestCase { + + private LauncherActivityInfo mSettingsApp; + + @Override + protected void setUp() throws Exception { + super.setUp(); + setDefaultLauncher(); + + mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) + .getActivityList("com.android.settings", Process.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)); + + // Find settings app and verify shortcuts appear when long pressed + UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())); + // Press icon center until shortcuts appear + Point iconCenter = icon.getVisibleCenter(); + sendPointer(MotionEvent.ACTION_DOWN, iconCenter); + UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container); + assertNotNull(deepShortcutsContainer); + sendPointer(MotionEvent.ACTION_UP, iconCenter); + + // Verify that launching a shortcut opens a page with the same text + assertTrue(deepShortcutsContainer.getChildCount() > 0); + UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0) + .findObject(getSelectorForId(R.id.bubble_text)); + shortcut.click(); + assertTrue(mDevice.wait(Until.hasObject(By.pkg( + mSettingsApp.getComponentName().getPackageName()) + .text(shortcut.getText())), DEFAULT_UI_TIMEOUT)); + } +} diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java new file mode 100644 index 000000000..5d86d1ec6 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java @@ -0,0 +1,75 @@ +package com.android.launcher3.ui; + +import android.content.pm.LauncherActivityInfo; +import android.graphics.Point; +import android.os.Process; +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 android.view.MotionEvent; + +import com.android.launcher3.R; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.Wait; + +/** + * Test for dragging a deep shortcut to the home screen. + */ +@LargeTest +public class ShortcutsToHomeTest extends LauncherInstrumentationTestCase { + + private LauncherActivityInfo mSettingsApp; + + @Override + protected void setUp() throws Exception { + super.setUp(); + setDefaultLauncher(); + + mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext) + .getActivityList("com.android.settings", Process.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)); + + // Find the app and long press it to show shortcuts. + UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())); + // Press icon center until shortcuts appear + Point iconCenter = icon.getVisibleCenter(); + sendPointer(MotionEvent.ACTION_DOWN, iconCenter); + UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container); + assertNotNull(deepShortcutsContainer); + sendPointer(MotionEvent.ACTION_UP, iconCenter); + + // Drag the first shortcut to the home screen. + assertTrue(deepShortcutsContainer.getChildCount() > 0); + UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0) + .findObject(getSelectorForId(R.id.bubble_text)); + String shortcutName = shortcut.getText(); + dragToWorkspace(shortcut, false); + + // Verify that the shortcut works on home screen + // (the app opens and has the same text as the shortcut). + mDevice.findObject(By.text(shortcutName)).click(); + assertTrue(mDevice.wait(Until.hasObject(By.pkg( + mSettingsApp.getComponentName().getPackageName()) + .text(shortcutName)), DEFAULT_UI_TIMEOUT)); + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java new file mode 100644 index 000000000..0b4e34f94 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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.launcher3.ui.widget; + +import android.app.Activity; +import android.app.Application; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +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.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Workspace; +import com.android.launcher3.testcomponent.WidgetConfigActivity; +import com.android.launcher3.ui.LauncherInstrumentationTestCase; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.SimpleActivityMonitor; +import com.android.launcher3.util.Wait; +import com.android.launcher3.widget.WidgetCell; + +import java.util.concurrent.Callable; + +/** + * Test to verify widget configuration is properly shown. + */ +@LargeTest +public class AddConfigWidgetTest extends LauncherInstrumentationTestCase { + + private LauncherAppWidgetProviderInfo mWidgetInfo; + private SimpleActivityMonitor mActivityMonitor; + private MainThreadExecutor mMainThreadExecutor; + private AppWidgetManager mAppWidgetManager; + + private int mWidgetId; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mWidgetInfo = findWidgetProvider(true /* hasConfigureScreen */); + mActivityMonitor = new SimpleActivityMonitor(); + ((Application) getInstrumentation().getTargetContext().getApplicationContext()) + .registerActivityLifecycleCallbacks(mActivityMonitor); + mMainThreadExecutor = new MainThreadExecutor(); + mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext); + + grantWidgetPermission(); + } + + @Override + protected void tearDown() throws Exception { + ((Application) getInstrumentation().getTargetContext().getApplicationContext()) + .unregisterActivityLifecycleCallbacks(mActivityMonitor); + super.tearDown(); + } + + public void testWidgetConfig() throws Throwable { + runTest(false, true); + } + + public void testWidgetConfig_rotate() throws Throwable { + runTest(true, true); + } + + public void testConfigCancelled() throws Throwable { + runTest(false, false); + } + + public void testConfigCancelled_rotate() throws Throwable { + runTest(true, false); + } + + /** + * @param rotateConfig should the config screen be rotated + * @param acceptConfig accept the config activity + */ + private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable { + lockRotation(true); + + clearHomescreen(); + startLauncher(); + + // Open widget tray and wait for load complete. + final UiObject2 widgetContainer = openWidgetsTray(); + assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT)); + + // Drag widget to homescreen + WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); + UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class) + .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager())))); + dragToWorkspace(widget, false); + // Widget id for which the config activity was opened + mWidgetId = monitor.getWidgetId(); + + if (rotateConfig) { + // Rotate the screen and verify that the config activity is recreated + monitor = new WidgetConfigStartupMonitor(); + lockRotation(false); + assertEquals(mWidgetId, monitor.getWidgetId()); + } + + // Verify that the widget id is valid and bound + assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); + + setResult(acceptConfig); + if (acceptConfig) { + assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT)); + assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); + } else { + // Verify that the widget id is deleted. + assertTrue(Wait.atMost(new Condition() { + @Override + public boolean isTrue() throws Throwable { + return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null; + } + }, DEFAULT_ACTIVITY_TIMEOUT)); + } + } + + private void setResult(boolean success) { + + getInstrumentation().getTargetContext().sendBroadcast( + WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class, + success ? "clickOK" : "clickCancel")); + } + + /** + * Condition for searching widget id + */ + private class WidgetSearchCondition extends Condition + implements Callable<Boolean>, Workspace.ItemOperator { + + @Override + public boolean isTrue() throws Throwable { + return mMainThreadExecutor.submit(this).get(); + } + + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).providerName.equals(mWidgetInfo.provider) && + ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId; + } + + @Override + public Boolean call() throws Exception { + // Find the resumed launcher + Launcher launcher = null; + for (Activity a : mActivityMonitor.resumed) { + if (a instanceof Launcher) { + launcher = (Launcher) a; + } + } + if (launcher == null) { + return false; + } + return launcher.getWorkspace().getFirstMatch(this) != null; + } + } + + /** + * Broadcast receiver for receiving widget config activity status. + */ + private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { + + public WidgetConfigStartupMonitor() { + super(WidgetConfigActivity.class.getName()); + } + + public int getWidgetId() throws InterruptedException { + Intent intent = blockingGetExtraIntent(); + assertNotNull(intent); + assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction()); + int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, + LauncherAppWidgetInfo.NO_ID); + assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID); + return widgetId; + } + } +} diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index a0ca60c29..3c92c578d 100644 --- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -1,16 +1,31 @@ -package com.android.launcher3.ui; +/* + * Copyright (C) 2017 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.launcher3.ui.widget; 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.ui.LauncherInstrumentationTestCase; import com.android.launcher3.util.Condition; import com.android.launcher3.util.Wait; import com.android.launcher3.widget.WidgetCell; @@ -26,6 +41,7 @@ public class AddWidgetTest extends LauncherInstrumentationTestCase { @Override protected void setUp() throws Exception { super.setUp(); + grantWidgetPermission(); widgetInfo = findWidgetProvider(false /* hasConfigureScreen */); } @@ -51,7 +67,7 @@ public class AddWidgetTest extends LauncherInstrumentationTestCase { // Drag widget to homescreen UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class) .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager())))); - dragToWorkspace(widget); + dragToWorkspace(widget, false); assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() { @Override diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java new file mode 100644 index 000000000..df2b66285 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2017 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.launcher3.ui.widget; + +import android.appwidget.AppWidgetHost; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Bundle; +import android.support.test.uiautomator.UiSelector; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetHostView; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.PendingAppWidgetHostView; +import com.android.launcher3.Workspace; +import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.ui.LauncherInstrumentationTestCase; +import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.LooperExecuter; +import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.WidgetHostViewLoader; + +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for bind widget flow. + * + * Note running these tests will clear the workspace on the device. + */ +@LargeTest +public class BindWidgetTest extends LauncherInstrumentationTestCase { + + private ContentResolver mResolver; + private AppWidgetManagerCompat mWidgetManager; + + // Objects created during test, which should be cleaned up in the end. + private Cursor mCursor; + // App install session id. + private int mSessionId = -1; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mResolver = mTargetContext.getContentResolver(); + mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext); + grantWidgetPermission(); + + // Clear all existing data + LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); + LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + if (mCursor != null) { + mCursor.close(); + } + + if (mSessionId > -1) { + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + } + } + + public void testBindNormalWidget_withConfig() { + LauncherAppWidgetProviderInfo info = findWidgetProvider(true); + LauncherAppWidgetInfo item = createWidgetInfo(info, true); + + setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); + } + + public void testBindNormalWidget_withoutConfig() { + LauncherAppWidgetProviderInfo info = findWidgetProvider(false); + LauncherAppWidgetInfo item = createWidgetInfo(info, true); + + setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); + } + + public void testUnboundWidget_removed() throws Exception { + LauncherAppWidgetProviderInfo info = findWidgetProvider(false); + LauncherAppWidgetInfo item = createWidgetInfo(info, false); + item.appWidgetId = -33; + + // Since there is no widget to verify, just wait until the workspace is ready. + setupAndVerifyContents(item, Workspace.class, null); + + waitUntilLoaderIdle(); + // Item deleted from db + mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), + null, null, null, null, null); + assertEquals(0, mCursor.getCount()); + + // The view does not exist + assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists()); + } + + public void testPendingWidget_autoRestored() { + // A non-restored widget with no config screen gets restored automatically. + LauncherAppWidgetProviderInfo info = findWidgetProvider(false); + + // Do not bind the widget + LauncherAppWidgetInfo item = createWidgetInfo(info, false); + item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; + + setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); + } + + public void testPendingWidget_withConfigScreen() throws Exception { + // A non-restored widget with config screen get bound and shows a 'Click to setup' UI. + LauncherAppWidgetProviderInfo info = findWidgetProvider(true); + + // Do not bind the widget + LauncherAppWidgetInfo item = createWidgetInfo(info, false); + item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; + + setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); + waitUntilLoaderIdle(); + // Item deleted from db + mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), + null, null, null, null, null); + mCursor.moveToNext(); + + // Widget has a valid Id now. + assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); + assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( + LauncherSettings.Favorites.APPWIDGET_ID)))); + } + + public void testPendingWidget_notRestored_removed() throws Exception { + LauncherAppWidgetInfo item = getInvalidWidgetInfo(); + item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; + + setupAndVerifyContents(item, Workspace.class, null); + // The view does not exist + assertFalse(mDevice.findObject( + new UiSelector().className(PendingAppWidgetHostView.class)).exists()); + waitUntilLoaderIdle(); + // Item deleted from db + mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), + null, null, null, null, null); + assertEquals(0, mCursor.getCount()); + } + + public void testPendingWidget_notRestored_brokenInstall() throws Exception { + // A widget which is was being installed once, even if its not being + // installed at the moment is not removed. + LauncherAppWidgetInfo item = getInvalidWidgetInfo(); + item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED + | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; + + setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); + // Verify item still exists in db + waitUntilLoaderIdle(); + mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), + null, null, null, null, null); + assertEquals(1, mCursor.getCount()); + + // Widget still has an invalid id. + mCursor.moveToNext(); + assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, + mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); + } + + public void testPendingWidget_notRestored_activeInstall() throws Exception { + // A widget which is being installed is not removed + LauncherAppWidgetInfo item = getInvalidWidgetInfo(); + item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; + + // Create an active installer session + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(item.providerName.getPackageName()); + PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); + mSessionId = installer.createSession(params); + + setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); + // Verify item still exists in db + waitUntilLoaderIdle(); + mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), + null, null, null, null, null); + assertEquals(1, mCursor.getCount()); + + // Widget still has an invalid id. + mCursor.moveToNext(); + assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, + mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); + } + + /** + * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the + * widget class is displayed on the homescreen. + * @param widgetClass the View class which is displayed on the homescreen + * @param desc the content description of the view or null. + */ + private void setupAndVerifyContents( + LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) { + long screenId = Workspace.FIRST_SCREEN_ID; + // Update the screen id counter for the provider. + LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); + + if (screenId > Workspace.FIRST_SCREEN_ID) { + screenId = Workspace.FIRST_SCREEN_ID; + } + ContentValues v = new ContentValues(); + v.put(LauncherSettings.WorkspaceScreens._ID, screenId); + v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0); + mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v); + + // Insert the item + ContentWriter writer = new ContentWriter(mTargetContext); + item.id = LauncherSettings.Settings.call( + mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); + item.screenId = screenId; + item.onAddToDatabase(writer); + writer.put(LauncherSettings.Favorites._ID, item.id); + mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext)); + resetLoaderState(); + + // Launch the home activity + 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_UI_TIMEOUT)); + } + + /** + * Creates a LauncherAppWidgetInfo corresponding to {@param info} + * @param bindWidget if true the info is bound and a valid widgetId is assigned to + * the LauncherAppWidgetInfo + */ + private LauncherAppWidgetInfo createWidgetInfo( + LauncherAppWidgetProviderInfo info, boolean bindWidget) { + LauncherAppWidgetInfo item = new LauncherAppWidgetInfo( + LauncherAppWidgetInfo.NO_ID, info.provider); + item.spanX = info.minSpanX; + item.spanY = info.minSpanY; + item.minSpanX = info.minSpanX; + item.minSpanY = info.minSpanY; + item.user = info.getUser(); + item.cellX = 0; + item.cellY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + + if (bindWidget) { + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info); + pendingInfo.spanX = item.spanX; + pendingInfo.spanY = item.spanY; + pendingInfo.minSpanX = item.minSpanX; + pendingInfo.minSpanY = item.minSpanY; + Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo); + + AppWidgetHost host = new AppWidgetHost(mTargetContext, Launcher.APPWIDGET_HOST_ID); + int widgetId = host.allocateAppWidgetId(); + if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) { + host.deleteAppWidgetId(widgetId); + throw new IllegalArgumentException("Unable to bind widget id"); + } + item.appWidgetId = widgetId; + } + return item; + } + + /** + * Returns a LauncherAppWidgetInfo with package name which is not present on the device + */ + private LauncherAppWidgetInfo getInvalidWidgetInfo() { + String invalidPackage = "com.invalidpackage"; + int count = 0; + String pkg = invalidPackage; + + Set<String> activePackage = getOnUiThread(new Callable<Set<String>>() { + @Override + public Set<String> call() throws Exception { + return PackageInstallerCompat.getInstance(mTargetContext) + .updateAndGetActiveSessionCache().keySet(); + } + }); + while(true) { + try { + mTargetContext.getPackageManager().getPackageInfo( + pkg, PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (Exception e) { + if (!activePackage.contains(pkg)) { + break; + } + } + pkg = invalidPackage + count; + count ++; + } + LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10, + new ComponentName(pkg, "com.test.widgetprovider")); + item.spanX = 2; + item.spanY = 2; + item.minSpanX = 2; + item.minSpanY = 2; + item.cellX = 0; + item.cellY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + /** + * Blocks the current thread until all the jobs in the main worker thread are complete. + */ + private void waitUntilLoaderIdle() throws Exception { + new LooperExecuter(LauncherModel.getWorkerLooper()) + .submit(new Runnable() { + @Override + public void run() { } + }).get(DEFAULT_WORKER_TIMEOUT_SECS, TimeUnit.SECONDS); + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java new file mode 100644 index 000000000..b798dfa88 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017 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.launcher3.ui.widget; + +import android.app.Activity; +import android.app.Application; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.graphics.Color; +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 android.view.View; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.testcomponent.AppWidgetNoConfig; +import com.android.launcher3.testcomponent.AppWidgetWithConfig; +import com.android.launcher3.testcomponent.RequestPinItemActivity; +import com.android.launcher3.ui.LauncherInstrumentationTestCase; +import com.android.launcher3.util.Condition; +import com.android.launcher3.util.SimpleActivityMonitor; +import com.android.launcher3.util.Wait; +import com.android.launcher3.widget.WidgetCell; + +import java.util.UUID; +import java.util.concurrent.Callable; + +/** + * Test to verify pin item request flow. + */ +@LargeTest +public class RequestPinItemTest extends LauncherInstrumentationTestCase { + + private SimpleActivityMonitor mActivityMonitor; + private MainThreadExecutor mMainThreadExecutor; + + private String mCallbackAction; + private String mShortcutId; + private int mAppWidgetId; + + @Override + protected void setUp() throws Exception { + super.setUp(); + grantWidgetPermission(); + setDefaultLauncher(); + + mActivityMonitor = new SimpleActivityMonitor(); + ((Application) getInstrumentation().getTargetContext().getApplicationContext()) + .registerActivityLifecycleCallbacks(mActivityMonitor); + mMainThreadExecutor = new MainThreadExecutor(); + + mCallbackAction = UUID.randomUUID().toString(); + mShortcutId = UUID.randomUUID().toString(); + } + + @Override + protected void tearDown() throws Exception { + ((Application) getInstrumentation().getTargetContext().getApplicationContext()) + .unregisterActivityLifecycleCallbacks(mActivityMonitor); + super.tearDown(); + } + + public void testPinWidgetNoConfig() throws Throwable { + runTest("pinWidgetNoConfig", true, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName()); + } + }); + } + + public void testPinWidgetNoConfig_customPreview() throws Throwable { + // Command to set custom preview + Intent command = RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setRemoteViewColor").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); + + runTest("pinWidgetNoConfig", true, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName()); + } + }, command); + } + + public void testPinWidgetWithConfig() throws Throwable { + runTest("pinWidgetWithConfig", true, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetWithConfig.class.getName()); + } + }); + } + + public void testPinShortcut() throws Throwable { + // Command to set the shortcut id + Intent command = RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setShortcutId").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", mShortcutId); + + runTest("pinShortcut", false, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof ShortcutInfo && + info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT && + ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId); + } + }, command); + } + + private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher, + Intent... commandIntents) throws Throwable { + if (!Utilities.isAtLeastO()) { + return; + } + lockRotation(true); + + clearHomescreen(); + startLauncher(); + + // Open all apps and wait for load complete + final UiObject2 appsContainer = openAllApps(); + assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT)); + + // Open Pin item activity + BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver( + RequestPinItemActivity.class.getName()); + scrollAndFind(appsContainer, By.text("Test Pin Item")).click(); + assertNotNull(openMonitor.blockingGetExtraIntent()); + + // Set callback + PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0, + new Intent(mCallbackAction), PendingIntent.FLAG_ONE_SHOT); + mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setCallback").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", callback)); + + for (Intent command : commandIntents) { + mTargetContext.sendBroadcast(command); + } + + // call the requested method to start the flow + mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, activityMethod)); + UiObject2 widgetCell = mDevice.wait( + Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT); + assertNotNull(widgetCell); + + // Accept confirmation: + BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction); + mDevice.wait(Until.findObject(By.text(mTargetContext.getString( + R.string.place_automatically).toUpperCase())), DEFAULT_UI_TIMEOUT).click(); + Intent result = resultReceiver.blockingGetIntent(); + assertNotNull(result); + mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + if (isWidget) { + assertNotSame(-1, mAppWidgetId); + } + + // Go back to home + mTargetContext.startActivity(getHomeIntent()); + assertTrue(Wait.atMost(new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT)); + } + + /** + * Condition for for an item + */ + private class ItemSearchCondition extends Condition implements Callable<Boolean> { + + private final ItemOperator mOp; + + ItemSearchCondition(ItemOperator op) { + mOp = op; + } + + @Override + public boolean isTrue() throws Throwable { + return mMainThreadExecutor.submit(this).get(); + } + + @Override + public Boolean call() throws Exception { + // Find the resumed launcher + Launcher launcher = null; + for (Activity a : mActivityMonitor.resumed) { + if (a instanceof Launcher) { + launcher = (Launcher) a; + } + } + if (launcher == null) { + return false; + } + return launcher.getWorkspace().getFirstMatch(mOp) != null; + } + } +} |