summaryrefslogtreecommitdiffstats
path: root/tests/src/com/android/launcher3/ui
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/com/android/launcher3/ui')
-rw-r--r--tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java8
-rw-r--r--tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java11
-rw-r--r--tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java215
-rw-r--r--tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java69
-rw-r--r--tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java75
-rw-r--r--tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java198
-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.java349
-rw-r--r--tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java229
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;
+ }
+ }
+}