diff options
author | Hyunyoung Song <hyunyoungs@google.com> | 2017-05-10 13:53:50 -0700 |
---|---|---|
committer | Hyunyoung Song <hyunyoungs@google.com> | 2017-05-10 16:52:47 -0700 |
commit | 311ec8962bef83bc240809e8365722a3a57dfec0 (patch) | |
tree | 001a6636ce1d55bd2e31ea69aa4ed2202591795d /tests | |
parent | 090ce68ceecc09780db4797277a3aaf8c7546a0b (diff) | |
parent | 5cfde85cb6d095585b932d9e3e2c4fe78aa0dafe (diff) | |
download | android_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')
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); + } +} |