summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorHyunyoung Song <hyunyoungs@google.com>2017-05-10 13:53:50 -0700
committerHyunyoung Song <hyunyoungs@google.com>2017-05-10 16:52:47 -0700
commit311ec8962bef83bc240809e8365722a3a57dfec0 (patch)
tree001a6636ce1d55bd2e31ea69aa4ed2202591795d /tests
parent090ce68ceecc09780db4797277a3aaf8c7546a0b (diff)
parent5cfde85cb6d095585b932d9e3e2c4fe78aa0dafe (diff)
downloadandroid_packages_apps_Trebuchet-311ec8962bef83bc240809e8365722a3a57dfec0.tar.gz
android_packages_apps_Trebuchet-311ec8962bef83bc240809e8365722a3a57dfec0.tar.bz2
android_packages_apps_Trebuchet-311ec8962bef83bc240809e8365722a3a57dfec0.zip
merged ub-launcher3-dorval, and resolved conflicts
Bug: 36904684 Bug: 37929893 Bug: 36068989 Test: make -j 32 dist checkbuild Change-Id: If9b11b212852cb1048d54db2224dab4acf2d93e0
Diffstat (limited to 'tests')
-rw-r--r--tests/Android.mk5
-rw-r--r--tests/AndroidManifest-common.xml63
-rw-r--r--tests/AndroidManifest.xml1
-rw-r--r--tests/res/drawable/test_drawable_pin_item.xml38
-rw-r--r--tests/res/layout/test_layout_appwidget_blue.xml7
-rw-r--r--tests/res/layout/test_layout_appwidget_red.xml7
-rw-r--r--tests/res/layout/test_layout_appwidget_view.xml14
-rw-r--r--tests/res/raw/cache_data_updated_task_data.txt28
-rw-r--r--tests/res/raw/package_install_state_change_task_data.txt24
-rw-r--r--tests/res/xml/appwidget_no_config.xml10
-rw-r--r--tests/res/xml/appwidget_with_config.xml11
-rw-r--r--tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java183
-rw-r--r--tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java226
-rw-r--r--tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java82
-rw-r--r--tests/src/com/android/launcher3/model/LoaderCursorTest.java236
-rw-r--r--tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java61
-rw-r--r--tests/src/com/android/launcher3/popup/PopupPopulatorTest.java (renamed from tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java)61
-rw-r--r--tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java26
-rw-r--r--tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java23
-rw-r--r--tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java126
-rw-r--r--tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java105
-rw-r--r--tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java44
-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.java (renamed from tests/src/com/android/launcher3/BindWidgetTest.java)74
-rw-r--r--tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java229
-rw-r--r--tests/src/com/android/launcher3/util/FocusLogicTest.java2
-rw-r--r--tests/src/com/android/launcher3/util/SimpleActivityMonitor.java65
33 files changed, 2216 insertions, 133 deletions
diff --git a/tests/Android.mk b/tests/Android.mk
index c91e793fc..6e6453ca1 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,11 +20,14 @@ LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
ub-uiautomator \
- legacy-android-test
+ legacy-android-test \
+ mockito-target-minus-junit4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
new file mode 100644
index 000000000..0a2914772
--- /dev/null
+++ b/tests/AndroidManifest-common.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.tests">
+
+ <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+ android:label="No Config">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_no_config" />
+ </receiver>
+
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+ android:label="With Config">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_with_config" />
+ </receiver>
+
+ <activity
+ android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
+ android:label="Test Pin Item"
+ android:icon="@drawable/test_drawable_pin_item">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index afe895297..43030aec9 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,6 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3.tests">
+ <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
<uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>
<application android:debuggable="true">
diff --git a/tests/res/drawable/test_drawable_pin_item.xml b/tests/res/drawable/test_drawable_pin_item.xml
new file mode 100644
index 000000000..1d0725690
--- /dev/null
+++ b/tests/res/drawable/test_drawable_pin_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+
+ <path
+ android:fillColor="#66000000"
+ android:pathData="M0,24a24,24 0 1,0 48,0a24,24 0 1,0 -48,0"/>
+ <path
+ android:fillColor="#FF1B5E20"
+ android:pathData="M2,24a22,22 0 1,0 44,0a22,22 0 1,0 -44,0"/>
+
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z"/>
+ </group>
+</vector> \ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_blue.xml b/tests/res/layout/test_layout_appwidget_blue.xml
new file mode 100644
index 000000000..8111978c9
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_blue.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FF0000FF"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_red.xml b/tests/res/layout/test_layout_appwidget_red.xml
new file mode 100644
index 000000000..48d3e8107
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_red.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#FFFF0000"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_view.xml b/tests/res/layout/test_layout_appwidget_view.xml
new file mode 100644
index 000000000..7c87e6019
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_view.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FFFF00">
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="20dp"
+ android:background="#FF0000"
+ android:id="@android:id/icon" />
+</FrameLayout> \ No newline at end of file
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
new file mode 100644
index 000000000..819968709
--- /dev/null
+++ b/tests/res/raw/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1 intent=component=app1/class1
+allApps componentName=app1/class2 intent=component=app1/class2
+allApps componentName=app2/class1 intent=component=app2/class1
+allApps componentName=app2/class2 intent=component=app2/class2 \ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
new file mode 100644
index 000000000..84f9c161e
--- /dev/null
+++ b/tests/res/raw/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10 \ No newline at end of file
diff --git a/tests/res/xml/appwidget_no_config.xml b/tests/res/xml/appwidget_no_config.xml
new file mode 100644
index 000000000..d24dfe34d
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_red"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider> \ No newline at end of file
diff --git a/tests/res/xml/appwidget_with_config.xml b/tests/res/xml/appwidget_with_config.xml
new file mode 100644
index 000000000..3e96c6f2f
--- /dev/null
+++ b/tests/res/xml/appwidget_with_config.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_blue"
+ android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider> \ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 000000000..d0ba9074c
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,183 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Provider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private ArrayList<Long> existingScreens;
+ private ArrayList<Long> newScreens;
+ private LongArrayMap<GridOccupancy> screenOccupancy;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ existingScreens = new ArrayList<>();
+ screenOccupancy = new LongArrayMap<>();
+ newScreens = new ArrayList<>();
+
+ idp.numColumns = 5;
+ idp.numRows = 5;
+ }
+
+ private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+ return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
+
+ @Override
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
+ };
+ }
+
+ public void testFindSpaceForItem_prefers_second() {
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
+ }
+
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound.first));
+ assertTrue(newScreens.contains(spaceFound.first));
+ }
+
+ public void testAddItem_existing_item_ignored() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ // Nothing was added
+ assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ public void testAddItem_some_items_added() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ ShortcutInfo info2 = new ShortcutInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
+ animated.capture(), any(ArrayList.class));
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+ occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ existingScreens.add(screenId);
+ screenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < idp.numColumns; x++) {
+ for (int y = 0; y < idp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(targetContext, info, false);
+ }
+ }
+ return startId;
+ }
+
+ private void commitScreensToDb() throws Exception {
+ LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ // Clear the table
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+ int count = existingScreens.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ long screenId = existingScreens.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+ getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 000000000..b9944db98
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,226 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+
+ public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+
+ public Context targetContext;
+ public UserHandle myUser;
+
+ public InvariantDeviceProfile idp;
+ public LauncherAppState appState;
+ public LauncherModel model;
+ public ModelWriter modelWriter;
+ public MyIconCache iconCache;
+
+ public BgDataModel bgDataModel;
+ public AllAppsList allAppsList;
+ public Callbacks callbacks;
+
+ public BaseModelUpdateTaskTestCase() {
+ super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ callbacks = mock(Callbacks.class);
+ appState = mock(LauncherAppState.class);
+ model = mock(LauncherModel.class);
+ modelWriter = mock(ModelWriter.class);
+ when(appState.getModel()).thenReturn(model);
+ when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
+
+ myUser = Process.myUserHandle();
+
+ bgDataModel = new BgDataModel();
+ targetContext = InstrumentationRegistry.getTargetContext();
+ idp = new InvariantDeviceProfile();
+ iconCache = new MyIconCache(targetContext, idp);
+
+ allAppsList = new AllAppsList(iconCache, new AppFilter());
+
+ when(appState.getIconCache()).thenReturn(iconCache);
+ when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+ }
+
+ /**
+ * Synchronously executes the task and returns all the UI callbacks posted.
+ */
+ public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
+ LauncherModel mockModel = mock(LauncherModel.class);
+ when(mockModel.getCallback()).thenReturn(callbacks);
+
+ Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
+ f.setAccessible(true);
+ f.set(task, mockModel);
+
+ DeferredHandler mockHandler = mock(DeferredHandler.class);
+ f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
+ f.setAccessible(true);
+ f.set(task, mockHandler);
+
+ task.execute(appState, bgDataModel, allAppsList);
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mockHandler, atLeast(0)).post(captor.capture());
+
+ return captor.getAllValues();
+ }
+
+ /**
+ * Initializes mock data for the test.
+ */
+ public void initializeData(String resourceName) throws Exception {
+ Context myContext = InstrumentationRegistry.getContext();
+ Resources res = myContext.getResources();
+ int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
+ String line;
+ HashMap<String, Class> classMap = new HashMap<>();
+ while((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+ String[] commands = line.split(" ");
+ switch (commands[0]) {
+ case "classMap":
+ classMap.put(commands[1], Class.forName(commands[2]));
+ break;
+ case "bgItem":
+ bgDataModel.addItem(targetContext,
+ (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+ break;
+ case "allApps":
+ allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
+ break;
+ }
+ }
+ }
+ }
+
+ private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+ HashMap<String, Field> cache = fieldCache.get(clazz);
+ if (cache == null) {
+ cache = new HashMap<>();
+ Class c = clazz;
+ while (c != null) {
+ for (Field f : c.getDeclaredFields()) {
+ f.setAccessible(true);
+ cache.put(f.getName(), f);
+ }
+ c = c.getSuperclass();
+ }
+ fieldCache.put(clazz, cache);
+ }
+
+ Object item = clazz.newInstance();
+ for (int i = startIndex; i < fieldDef.length; i++) {
+ String[] fieldData = fieldDef[i].split("=", 2);
+ Field f = cache.get(fieldData[0]);
+ Class type = f.getType();
+ if (type == int.class || type == long.class) {
+ f.set(item, Integer.parseInt(fieldData[1]));
+ } else if (type == CharSequence.class || type == String.class) {
+ f.set(item, fieldData[1]);
+ } else if (type == Intent.class) {
+ if (!fieldData[1].startsWith("#Intent")) {
+ fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+ }
+ f.set(item, Intent.parseUri(fieldData[1], 0));
+ } else if (type == ComponentName.class) {
+ f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+ } else {
+ throw new Exception("Added parsing logic for "
+ + f.getName() + " of type " + f.getType());
+ }
+ }
+ return item;
+ }
+
+ public static class MyIconCache extends IconCache {
+
+ private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+ public MyIconCache(Context context, InvariantDeviceProfile idp) {
+ super(context, idp);
+ }
+
+ @Override
+ protected CacheEntry cacheLocked(
+ @NonNull ComponentName componentName,
+ @NonNull Provider<LauncherActivityInfo> infoProvider,
+ UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+ CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.icon = getDefaultIcon(user);
+ }
+ return entry;
+ }
+
+ public void addCache(ComponentName key, String title) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = newIcon();
+ entry.title = title;
+ mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
+ }
+
+ public Bitmap newIcon() {
+ return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ }
+
+ @Override
+ protected Bitmap makeDefaultIcon(UserHandle user) {
+ return newIcon();
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 000000000..d595e6cf1
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,82 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private static final String NEW_LABEL_PREFIX = "new-label-";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ initializeData("cache_data_updated_task_data");
+ // Add dummy entries in the cache to simulate update
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+ }
+ }
+
+ private CacheDataUpdatedTask newTask(int op, String... pkg) {
+ return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+ }
+
+ public void testCacheUpdate_update_apps() throws Exception {
+ // Clear all icons from apps list so that its easy to check what was updated
+ for (AppInfo info : allAppsList.data) {
+ info.iconBitmap = null;
+ }
+
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+
+ // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
+ // is not updated
+ verifyUpdate(1L, 2L);
+
+ // Verify that only app1 var updated in allAppsList
+ assertFalse(allAppsList.data.isEmpty());
+ for (AppInfo info : allAppsList.data) {
+ if (info.componentName.getPackageName().equals("app1")) {
+ assertNotNull(info.iconBitmap);
+ } else {
+ assertNull(info.iconBitmap);
+ }
+ }
+ }
+
+ public void testSessionUpdate_ignores_normal_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+ // app1 has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ }
+
+ public void testSessionUpdate_updates_pending_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+
+ // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
+ // were updated
+ verifyUpdate(5L, 6L);
+ }
+
+ private void verifyUpdate(Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (updates.contains(info.id)) {
+ assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNotNull(((ShortcutInfo) info).iconBitmap);
+ } else {
+ assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNull(((ShortcutInfo) info).iconBitmap);
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
new file mode 100644
index 000000000..173c55621
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -0,0 +1,236 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static com.android.launcher3.LauncherSettings.BaseLauncherColumns.INTENT;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
+import static com.android.launcher3.LauncherSettings.Favorites.RESTORED;
+import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
+import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
+import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link LoaderCursor}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LoaderCursorTest {
+
+ private LauncherAppState mMockApp;
+ private IconCache mMockIconCache;
+
+ private MatrixCursor mCursor;
+ private InvariantDeviceProfile mIDP;
+ private Context mContext;
+ private LauncherAppsCompat mLauncherApps;
+
+ private LoaderCursor mLoaderCursor;
+
+ @Before
+ public void setup() {
+ mIDP = new InvariantDeviceProfile();
+ mCursor = new MatrixCursor(new String[] {
+ ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
+ _ID, CONTAINER, ITEM_TYPE, PROFILE_ID,
+ SCREEN, CELLX, CELLY, RESTORED, INTENT
+ });
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ mMockApp = mock(LauncherAppState.class);
+ mMockIconCache = mock(IconCache.class);
+ when(mMockApp.getIconCache()).thenReturn(mMockIconCache);
+ when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP);
+ when(mMockApp.getContext()).thenReturn(mContext);
+ mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+
+ mLoaderCursor = new LoaderCursor(mCursor, mMockApp);
+ mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+ }
+
+ private void initCursor(int itemType, String title) {
+ mCursor.newRow()
+ .add(_ID, 1)
+ .add(PROFILE_ID, 0)
+ .add(ITEM_TYPE, itemType)
+ .add(TITLE, title)
+ .add(CONTAINER, CONTAINER_DESKTOP);
+ }
+
+ @Test
+ public void getAppShortcutInfo_dontAllowMissing_invalidComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+ ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+ assertNull(mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), false /* allowMissingTarget */, true));
+ }
+
+ @Test
+ public void getAppShortcutInfo_dontAllowMissing_validComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user)
+ .get(0).getComponentName();
+ ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), false /* allowMissingTarget */, true);
+ assertNotNull(info);
+ assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ }
+
+ @Test
+ public void getAppShortcutInfo_allowMissing_invalidComponent() {
+ initCursor(ITEM_TYPE_APPLICATION, "");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+ ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), true /* allowMissingTarget */, true);
+ assertNotNull(info);
+ assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ }
+
+ @Test
+ public void loadSimpleShortcut() {
+ initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
+ assertTrue(mLoaderCursor.moveToNext());
+
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon);
+ ShortcutInfo info = mLoaderCursor.loadSimpleShortcut();
+ assertEquals(icon, info.iconBitmap);
+ assertEquals("my-shortcut", info.title);
+ assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
+ }
+
+ @Test
+ public void checkItemPlacement_wrongWorkspaceScreen() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 3L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Item on unknown screen are not placed
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3L), workspaceScreens));
+
+ }
+ @Test
+ public void checkItemPlacement_outsideBounds() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Item outside screen bounds are not placed
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ }
+
+ @Test
+ public void checkItemPlacement_overlappingItems() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Overlapping items are not placed
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+ }
+
+ @Test
+ public void checkItemPlacement_hotseat() {
+ ArrayList<Long> workspaceScreens = new ArrayList<>();
+ mIDP.numRows = 4;
+ mIDP.numColumns = 4;
+ mIDP.numHotseatIcons = 3;
+
+ // Hotseat items are only placed based on screenId
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1L), workspaceScreens));
+ assertTrue(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2L), workspaceScreens));
+
+ assertFalse(mLoaderCursor.checkItemPlacement(
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3L), workspaceScreens));
+ }
+
+ private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
+ long container, long screenId) {
+ ItemInfo info = new ItemInfo();
+ info.cellX = cellX;
+ info.cellY = cellY;
+ info.spanX = spanX;
+ info.spanY = spanY;
+ info.container = container;
+ info.screenId = screenId;
+ return info;
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 000000000..d6555620c
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,61 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initializeData("package_install_state_change_task_data");
+ }
+
+ private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
+ installInfo.progress = progress;
+ installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
+ return new PackageInstallStateChangedTask(installInfo);
+ }
+
+ public void testSessionUpdate_ignore_installed() throws Exception {
+ executeTaskForTest(newTask("app1", 30));
+
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ }
+
+ public void testSessionUpdate_shortcuts_updated() throws Exception {
+ executeTaskForTest(newTask("app3", 30));
+
+ verifyProgressUpdate(30, 5L, 6L, 7L);
+ }
+
+ public void testSessionUpdate_widgets_updated() throws Exception {
+ executeTaskForTest(newTask("app4", 30));
+
+ verifyProgressUpdate(30, 8L, 9L);
+ }
+
+ private void verifyProgressUpdate(int progress, Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ assertEquals(updates.contains(info.id) ? progress: 0,
+ ((ShortcutInfo) info).getInstallProgress());
+ } else {
+ assertEquals(updates.contains(info.id) ? progress: -1,
+ ((LauncherAppWidgetInfo) info).installProgress);
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 05d0ffb8c..2ad9b35ae 100644
--- a/tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java
+++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.launcher3.shortcuts;
+package com.android.launcher3.popup;
import android.content.pm.ShortcutInfo;
import android.support.test.runner.AndroidJUnit4;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -26,40 +28,64 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static com.android.launcher3.shortcuts.ShortcutFilter.MAX_SHORTCUTS;
-import static com.android.launcher3.shortcuts.ShortcutFilter.NUM_DYNAMIC;
+import static com.android.launcher3.popup.PopupPopulator.MAX_ITEMS;
+import static com.android.launcher3.popup.PopupPopulator.NUM_DYNAMIC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
- * Tests the sorting and filtering of shortcuts in {@link ShortcutFilter}.
+ * Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
@RunWith(AndroidJUnit4.class)
-public class ShortcutFilterTest {
+public class PopupPopulatorTest {
@Test
public void testSortAndFilterShortcuts() {
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 3);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_SHORTCUTS, 0);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_SHORTCUTS);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_ITEMS, 0);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_ITEMS);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 3),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 5),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_SHORTCUTS - 1, 1);
- filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_SHORTCUTS - 1);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_ITEMS - 1, 1);
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_ITEMS - 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 3),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 5),
- MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+ MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+ }
+
+ @Test
+ public void testDeDupeShortcutId() {
+ // Successfully remove one of the shortcuts
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 2, 0, generateId(true, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 2, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 1, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 1, 2, generateId(true, 1));
+ // Successfully keep all shortcuts when id doesn't exist
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(false, 1));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(true, 4));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(false, 4));
+ filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(true, 4));
+ }
+
+ private String generateId(boolean isStatic, int rank) {
+ return (isStatic ? "static" : "dynamic") + rank;
}
private void filterShortcutsAndAssertNumStaticAndDynamic(
List<ShortcutInfoCompat> shortcuts, int expectedStatic, int expectedDynamic) {
+ filterShortcutsAndAssertNumStaticAndDynamic(shortcuts, expectedStatic, expectedDynamic, null);
+ }
+
+ private void filterShortcutsAndAssertNumStaticAndDynamic(List<ShortcutInfoCompat> shortcuts,
+ int expectedStatic, int expectedDynamic, String shortcutIdToRemove) {
Collections.shuffle(shortcuts);
- List<ShortcutInfoCompat> filteredShortcuts = ShortcutFilter.sortAndFilterShortcuts(shortcuts);
+ List<ShortcutInfoCompat> filteredShortcuts = PopupPopulator.sortAndFilterShortcuts(
+ shortcuts, shortcutIdToRemove);
assertIsSorted(filteredShortcuts);
int numStatic = 0;
@@ -110,6 +136,7 @@ public class ShortcutFilterTest {
private class Shortcut extends ShortcutInfoCompat {
private boolean mIsStatic;
private int mRank;
+ private String mId;
public Shortcut(ShortcutInfo shortcutInfo) {
super(shortcutInfo);
@@ -119,6 +146,7 @@ public class ShortcutFilterTest {
this(null);
mIsStatic = isStatic;
mRank = rank;
+ mId = generateId(isStatic, rank);
}
@Override
@@ -135,5 +163,10 @@ public class ShortcutFilterTest {
public int getRank() {
return mRank;
}
+
+ @Override
+ public String getId() {
+ return mId;
+ }
}
} \ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
new file mode 100644
index 000000000..9b320d83b
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget without any configuration screen.
+ */
+public class AppWidgetNoConfig extends AppWidgetProvider {
+
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
new file mode 100644
index 000000000..033e6e674
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
@@ -0,0 +1,23 @@
+/*
+ * 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.testcomponent;
+
+/**
+ * A simple app widget with configuration sceen.
+ */
+public class AppWidgetWithConfig extends AppWidgetNoConfig {
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
new file mode 100644
index 000000000..904590cb8
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -0,0 +1,126 @@
+/*
+ * 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.testcomponent;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base activity with utility methods to help automate testing.
+ */
+public class BaseTestingActivity extends Activity implements View.OnClickListener {
+
+ public static final String SUFFIX_COMMAND = "-command";
+ public static final String EXTRA_METHOD = "method";
+ public static final String EXTRA_PARAM = "param_";
+
+ private static final int MARGIN_DP = 20;
+
+ private final String mAction = this.getClass().getName();
+
+ private LinearLayout mView;
+ private int mMargin;
+
+ private final BroadcastReceiver mCommandReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleCommand(intent);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mMargin = Math.round(TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, MARGIN_DP, getResources().getDisplayMetrics()));
+ mView = new LinearLayout(this);
+ mView.setPadding(mMargin, mMargin, mMargin, mMargin);
+ mView.setOrientation(LinearLayout.VERTICAL);
+ setContentView(mView);
+
+ registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
+ }
+
+ protected void addButton(String title, String method) {
+ Button button = new Button(this);
+ button.setText(title);
+ button.setTag(method);
+ button.setOnClickListener(this);
+
+ LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ lp.bottomMargin = mMargin;
+ mView.addView(button, lp);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mCommandReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onClick(View view) {
+ handleCommand(new Intent().putExtra(EXTRA_METHOD, (String) view.getTag()));
+ }
+
+ private void handleCommand(Intent cmd) {
+ String methodName = cmd.getStringExtra(EXTRA_METHOD);
+ try {
+ Method method = null;
+ for (Method m : this.getClass().getDeclaredMethods()) {
+ if (methodName.equals(m.getName()) &&
+ !Modifier.isStatic(m.getModifiers()) &&
+ Modifier.isPublic(m.getModifiers())) {
+ method = m;
+ break;
+ }
+ }
+ Object[] args = new Object[method.getParameterTypes().length];
+ Bundle extras = cmd.getExtras();
+ for (int i = 0; i < args.length; i++) {
+ args[i] = extras.get(EXTRA_PARAM + i);
+ }
+ method.invoke(this, args);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Intent getCommandIntent(Class<?> clazz, String method) {
+ return new Intent(clazz.getName() + SUFFIX_COMMAND)
+ .putExtra(EXTRA_METHOD, method);
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
new file mode 100644
index 000000000..85809922e
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
@@ -0,0 +1,105 @@
+/*
+ * 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.testcomponent;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+/**
+ * Sample activity to request pinning an item.
+ */
+@TargetApi(26)
+public class RequestPinItemActivity extends BaseTestingActivity {
+
+ private PendingIntent mCallback = null;
+ private String mShortcutId = "test-id";
+ private int mRemoteViewColor = Color.TRANSPARENT;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addButton("Pin Shortcut", "pinShortcut");
+ addButton("Pin Widget without config ", "pinWidgetNoConfig");
+ addButton("Pin Widget with config", "pinWidgetWithConfig");
+ }
+
+ public void setCallback(PendingIntent callback) {
+ mCallback = callback;
+ }
+
+ public void setRemoteViewColor(int color) {
+ mRemoteViewColor = color;
+ }
+
+ public void setShortcutId(String id) {
+ mShortcutId = id;
+ }
+
+ public void pinShortcut() {
+ ShortcutManager sm = getSystemService(ShortcutManager.class);
+
+ // Generate icon
+ int r = sm.getIconMaxWidth() / 2;
+ Bitmap icon = Bitmap.createBitmap(r * 2, r * 2, Bitmap.Config.ARGB_8888);
+ Paint p = new Paint();
+ p.setColor(Color.RED);
+ new Canvas(icon).drawCircle(r, r, r, p);
+
+ ShortcutInfo info = new ShortcutInfo.Builder(this, mShortcutId)
+ .setIntent(getPackageManager().getLaunchIntentForPackage(getPackageName()))
+ .setIcon(Icon.createWithBitmap(icon))
+ .setShortLabel("Test shortcut")
+ .build();
+
+ IntentSender callback = mCallback == null ? null : mCallback.getIntentSender();
+ sm.requestPinShortcut(info, callback);
+ }
+
+ public void pinWidgetNoConfig() {
+ requestWidget(new ComponentName(this, AppWidgetNoConfig.class));
+ }
+
+ public void pinWidgetWithConfig() {
+ requestWidget(new ComponentName(this, AppWidgetWithConfig.class));
+ }
+
+ private void requestWidget(ComponentName cn) {
+ Bundle extras = null;
+ if (mRemoteViewColor != Color.TRANSPARENT) {
+ int layoutId = getResources().getIdentifier(
+ "test_layout_appwidget_view", "layout", getPackageName());
+ RemoteViews views = new RemoteViews(getPackageName(), layoutId);
+ views.setInt(android.R.id.icon, "setBackgroundColor", mRemoteViewColor);
+ extras = new Bundle();
+ extras.putParcelable(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW, views);
+ }
+
+ AppWidgetManager.getInstance(this).requestPinAppWidget(cn, extras, mCallback);
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
new file mode 100644
index 000000000..d76ad0499
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testcomponent;
+
+import android.os.Bundle;
+
+/**
+ * Simple activity for widget configuration
+ */
+public class WidgetConfigActivity extends BaseTestingActivity {
+
+ public static final String SUFFIX_FINISH = "-finish";
+ public static final String EXTRA_CODE = "code";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addButton("Cancel", "clickCancel");
+ addButton("OK", "clickOK");
+ }
+
+ public void clickCancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ public void clickOK() {
+ setResult(RESULT_OK);
+ finish();
+ }
+}
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/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 5c5069fea..df2b66285 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -1,6 +1,20 @@
-package com.android.launcher3;
+/*
+ * 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.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -9,15 +23,23 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.database.Cursor;
-import android.os.Build;
import android.os.Bundle;
import android.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.ManagedProfileHeuristic;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.LooperExecuter;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -32,7 +54,6 @@ import java.util.concurrent.TimeUnit;
* Note running these tests will clear the workspace on the device.
*/
@LargeTest
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class BindWidgetTest extends LauncherInstrumentationTestCase {
private ContentResolver mResolver;
@@ -218,28 +239,16 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
// Insert the item
- v = new ContentValues();
+ 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(mTargetContext, v);
- v.put(LauncherSettings.Favorites._ID, item.id);
- mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
-
- // Reset loader
- try {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- LauncherClings.markFirstRunClingDismissed(mTargetContext);
- ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
- LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
- }
- });
- } catch (Throwable t) {
- throw new IllegalArgumentException(t);
- }
+ 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
@@ -264,13 +273,13 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
item.spanY = info.minSpanY;
item.minSpanX = info.minSpanX;
item.minSpanY = info.minSpanY;
- item.user = mWidgetManager.getUser(info);
+ item.user = info.getUser();
item.cellX = 0;
item.cellY = 1;
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
if (bindWidget) {
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info);
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
@@ -330,14 +339,11 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
/**
* Blocks the current thread until all the jobs in the main worker thread are complete.
*/
- private void waitUntilLoaderIdle() throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- LauncherModel.sWorker.post(new Runnable() {
- @Override
- public void run() {
- latch.countDown();
- }
- });
- assertTrue(latch.await(5, TimeUnit.SECONDS));
+ 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;
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index eee567fb8..79aed806c 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -49,8 +49,6 @@ public final class FocusLogicTest extends AndroidTestCase {
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
}
public void testCreateSparseMatrix() {
diff --git a/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
new file mode 100644
index 000000000..6154ab6c5
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.app.Application.*;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Simple monitor to keep a list of active activities.
+ */
+public class SimpleActivityMonitor implements ActivityLifecycleCallbacks {
+
+ public final ArrayList<Activity> created = new ArrayList<>();
+ public final ArrayList<Activity> started = new ArrayList<>();
+ public final ArrayList<Activity> resumed = new ArrayList<>();
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ created.add(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ started.add(activity);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ resumed.add(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ resumed.remove(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ started.remove(activity);
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ created.remove(activity);
+ }
+}