diff options
Diffstat (limited to 'tests/src/com/android/launcher3/ui/widget')
4 files changed, 856 insertions, 0 deletions
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/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java new file mode 100644 index 000000000..3c92c578d --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -0,0 +1,80 @@ +/* + * 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.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; + +/** + * Test to add widget from widget tray + */ +@LargeTest +public class AddWidgetTest extends LauncherInstrumentationTestCase { + + private LauncherAppWidgetProviderInfo widgetInfo; + + @Override + protected void setUp() throws Exception { + super.setUp(); + grantWidgetPermission(); + + 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 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 + UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class) + .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager())))); + dragToWorkspace(widget, false); + + 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/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; + } + } +} |