summaryrefslogtreecommitdiffstats
path: root/tests/src/com/android/launcher3/ui/widget
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/com/android/launcher3/ui/widget')
-rw-r--r--tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java198
-rw-r--r--tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java80
-rw-r--r--tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java349
-rw-r--r--tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java229
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;
+ }
+ }
+}