Loading go/src/com/android/launcher3/model/WidgetsModel.java +8 −8 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherAppState; import com.android.launcher3.icons.ComponentWithLabelAndIcon; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetListRowEntry; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import java.util.ArrayList; import java.util.Collections; Loading @@ -43,17 +43,17 @@ public class WidgetsModel { public static final boolean GO_DISABLE_WIDGETS = true; public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true; private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>(); private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>(); /** * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s * is not sorted. This list is sorted at the UI when using * {@link com.android.launcher3.widget.WidgetsDiffReporter} * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is * not sorted. This list is sorted at the UI when using * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter} * * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List) */ public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) { public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) { return EMPTY_WIDGET_LIST; } Loading res/layout/widgets_full_sheet.xml +3 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> <com.android.launcher3.widget.WidgetsFullSheet <com.android.launcher3.widget.picker.WidgetsFullSheet xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" Loading @@ -27,7 +27,7 @@ android:background="?android:attr/colorPrimary" android:elevation="4dp"> <com.android.launcher3.widget.WidgetsRecyclerView <com.android.launcher3.widget.picker.WidgetsRecyclerView android:id="@+id/widgets_list_view" android:layout_width="match_parent" android:layout_height="match_parent" Loading @@ -49,4 +49,4 @@ android:layout_alignParentTop="true" android:layout_marginEnd="@dimen/fastscroll_end_margin" /> </com.android.launcher3.views.TopRoundedCornerView> </com.android.launcher3.widget.WidgetsFullSheet> No newline at end of file </com.android.launcher3.widget.picker.WidgetsFullSheet> No newline at end of file robolectric_tests/src/com/android/launcher3/testing/TestActivity.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.testing; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; /** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */ public class TestActivity extends BaseActivity implements ActivityContext { private DeviceProfile mDeviceProfile; @Override public BaseDragLayer getDragLayer() { return null; } @Override public DeviceProfile getDeviceProfile() { return mDeviceProfile; } public void setDeviceProfile(DeviceProfile deviceProfile) { mDeviceProfile = deviceProfile; } } robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder; import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.widget.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsFullSheet; import org.junit.Before; import org.junit.Test; Loading robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java→robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java +215 −0 Original line number Diff line number Diff line Loading @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.widget; package com.android.launcher3.widget.picker; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; Loading @@ -33,9 +34,12 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import org.junit.Before; import org.junit.Test; Loading @@ -48,10 +52,13 @@ import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) public class WidgetsListAdapterTest { public final class WidgetsListAdapterTest { private static final String TEST_PACKAGE_1 = "com.google.test.1"; private static final String TEST_PACKAGE_2 = "com.google.test.2"; @Mock private LayoutInflater mMockLayoutInflater; @Mock private WidgetPreviewLoader mMockWidgetCache; Loading @@ -72,80 +79,137 @@ public class WidgetsListAdapterTest { mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache, mIconCache, null, null); mAdapter.registerAdapterDataObserver(mListener); doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0)) .getComponent().getPackageName()) .when(mIconCache).getTitleNoCache(any()); } @Test public void test_notifyDataSetChanged() throws Exception { public void setWidgets_shouldNotifyDataSetChanged() { mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener).onChanged(); } @Test public void test_notifyItemInserted() throws Exception { public void setWidgets_withItemInserted_shouldNotifyItemInserted() { mAdapter.setWidgets(generateSampleMap(1)); mAdapter.setWidgets(generateSampleMap(2)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1)); verify(mListener).onItemRangeInserted(eq(1), eq(1)); } @Test public void test_notifyItemRemoved() throws Exception { public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() { mAdapter.setWidgets(generateSampleMap(2)); mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1)); verify(mListener).onItemRangeRemoved(eq(1), eq(1)); } @Test public void testNotifyItemChanged_PackageIconDiff() throws Exception { public void setWidgets_appIconChanged_shouldNotifyItemChanged() { mAdapter.setWidgets(generateSampleMap(1)); mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull()); verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull()); } @Test public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception { // TODO: same package name but item number changed public void setWidgets_sameApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() { // GIVEN the adapter was first populated with test package 1 & test package 2. WidgetsListBaseEntry testPackage1With2WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_1, /* numOfWidgets= */ 2); WidgetsListBaseEntry testPackage2With2WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2); mAdapter.setWidgets( List.of(testPackage1With2WidgetsListEntry, testPackage2With2WidgetsListEntry)); // WHEN the adapter is updated with the same list of apps but test package 2 has 3 widgets // now. WidgetsListBaseEntry testPackage1With3WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2); mAdapter.setWidgets( List.of(testPackage1With2WidgetsListEntry, testPackage1With3WidgetsListEntry)); // THEN the onItemRangeChanged is invoked. verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull()); } @Test public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception { // TODO: insert and remove combined. curMap // newMap [A, C, D] [A, B, E] // B - C < 0, removed B from index 1 [A, E] // E - C > 0, C inserted to index 1 [A, C, E] // E - D > 0, D inserted to index 2 [A, C, D, E] // E - null = -1, E deleted from index 3 [A, C, D] public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() { List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5); // GIVEN the current widgets list consist of [A, B, E]. List<WidgetsListBaseEntry> currentList = List.of( allAppsWithWidgets.get(0), allAppsWithWidgets.get(1), allAppsWithWidgets.get(4)); mAdapter.setWidgets(currentList); // WHEN the widgets list is updated to [A, C, D]. List<WidgetsListBaseEntry> newList = List.of( allAppsWithWidgets.get(0), allAppsWithWidgets.get(2), allAppsWithWidgets.get(3)); mAdapter.setWidgets(newList); // Computation logic | [Intermediate list during computation] // THEN B <> C < 0, removed B from index 1 | [A, E] verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1); // THEN E <> C > 0, C inserted to index 1 | [A, C, E] verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1); // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E] verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1); // THEN E <> null = -1, E deleted from index 3 | [A, C, D] verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1); } /** * Helper method to generate the sample widget model map that can be used for the tests * @param num the number of WidgetItem the map should contain */ private ArrayList<WidgetListRowEntry> generateSampleMap(int num) { ArrayList<WidgetListRowEntry> result = new ArrayList<>(); private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) { ArrayList<WidgetsListBaseEntry> result = new ArrayList<>(); if (num <= 0) return result; ShadowPackageManager spm = shadowOf(mContext.getPackageManager()); for (int i = 0; i < num; i++) { ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet"); AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo(); widgetInfo.provider = cn; ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn)); String packageName = "com.placeholder.apk" + i; WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache); List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1); PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); PackageItemInfo pInfo = new PackageItemInfo(packageName); pInfo.title = pInfo.packageName; pInfo.user = wi.user; pInfo.user = widgetItems.get(0).user; pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi)))); result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems)); } return result; } private WidgetsListBaseEntry generateSampleAppWithWidgets(String packageName, int numOfWidgets) { PackageItemInfo appInfo = new PackageItemInfo(packageName); appInfo.title = appInfo.packageName; appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); return new WidgetsListContentEntry(appInfo, /* titleSectionName= */ "", generateWidgetItems(packageName, numOfWidgets)); } private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) { ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager()); ArrayList<WidgetItem> widgetItems = new ArrayList<>(); for (int i = 0; i < numOfWidgets; i++) { ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i); AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo(); widgetInfo.provider = cn; ReflectionHelpers.setField(widgetInfo, "providerInfo", packageManager.addReceiverIfNotPresent(cn)); widgetItems.add(new WidgetItem( LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache)); } return widgetItems; } } Loading
go/src/com/android/launcher3/model/WidgetsModel.java +8 −8 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherAppState; import com.android.launcher3.icons.ComponentWithLabelAndIcon; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetListRowEntry; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import java.util.ArrayList; import java.util.Collections; Loading @@ -43,17 +43,17 @@ public class WidgetsModel { public static final boolean GO_DISABLE_WIDGETS = true; public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true; private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>(); private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>(); /** * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s * is not sorted. This list is sorted at the UI when using * {@link com.android.launcher3.widget.WidgetsDiffReporter} * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is * not sorted. This list is sorted at the UI when using * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter} * * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List) */ public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) { public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) { return EMPTY_WIDGET_LIST; } Loading
res/layout/widgets_full_sheet.xml +3 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> <com.android.launcher3.widget.WidgetsFullSheet <com.android.launcher3.widget.picker.WidgetsFullSheet xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" Loading @@ -27,7 +27,7 @@ android:background="?android:attr/colorPrimary" android:elevation="4dp"> <com.android.launcher3.widget.WidgetsRecyclerView <com.android.launcher3.widget.picker.WidgetsRecyclerView android:id="@+id/widgets_list_view" android:layout_width="match_parent" android:layout_height="match_parent" Loading @@ -49,4 +49,4 @@ android:layout_alignParentTop="true" android:layout_marginEnd="@dimen/fastscroll_end_margin" /> </com.android.launcher3.views.TopRoundedCornerView> </com.android.launcher3.widget.WidgetsFullSheet> No newline at end of file </com.android.launcher3.widget.picker.WidgetsFullSheet> No newline at end of file
robolectric_tests/src/com/android/launcher3/testing/TestActivity.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.testing; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; /** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */ public class TestActivity extends BaseActivity implements ActivityContext { private DeviceProfile mDeviceProfile; @Override public BaseDragLayer getDragLayer() { return null; } @Override public DeviceProfile getDeviceProfile() { return mDeviceProfile; } public void setDeviceProfile(DeviceProfile deviceProfile) { mDeviceProfile = deviceProfile; } }
robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder; import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.widget.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsFullSheet; import org.junit.Before; import org.junit.Test; Loading
robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java→robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java +215 −0 Original line number Diff line number Diff line Loading @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.widget; package com.android.launcher3.widget.picker; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; Loading @@ -33,9 +34,12 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import org.junit.Before; import org.junit.Test; Loading @@ -48,10 +52,13 @@ import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) public class WidgetsListAdapterTest { public final class WidgetsListAdapterTest { private static final String TEST_PACKAGE_1 = "com.google.test.1"; private static final String TEST_PACKAGE_2 = "com.google.test.2"; @Mock private LayoutInflater mMockLayoutInflater; @Mock private WidgetPreviewLoader mMockWidgetCache; Loading @@ -72,80 +79,137 @@ public class WidgetsListAdapterTest { mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache, mIconCache, null, null); mAdapter.registerAdapterDataObserver(mListener); doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0)) .getComponent().getPackageName()) .when(mIconCache).getTitleNoCache(any()); } @Test public void test_notifyDataSetChanged() throws Exception { public void setWidgets_shouldNotifyDataSetChanged() { mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener).onChanged(); } @Test public void test_notifyItemInserted() throws Exception { public void setWidgets_withItemInserted_shouldNotifyItemInserted() { mAdapter.setWidgets(generateSampleMap(1)); mAdapter.setWidgets(generateSampleMap(2)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1)); verify(mListener).onItemRangeInserted(eq(1), eq(1)); } @Test public void test_notifyItemRemoved() throws Exception { public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() { mAdapter.setWidgets(generateSampleMap(2)); mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1)); verify(mListener).onItemRangeRemoved(eq(1), eq(1)); } @Test public void testNotifyItemChanged_PackageIconDiff() throws Exception { public void setWidgets_appIconChanged_shouldNotifyItemChanged() { mAdapter.setWidgets(generateSampleMap(1)); mAdapter.setWidgets(generateSampleMap(1)); verify(mListener, times(1)).onChanged(); verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull()); verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull()); } @Test public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception { // TODO: same package name but item number changed public void setWidgets_sameApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() { // GIVEN the adapter was first populated with test package 1 & test package 2. WidgetsListBaseEntry testPackage1With2WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_1, /* numOfWidgets= */ 2); WidgetsListBaseEntry testPackage2With2WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2); mAdapter.setWidgets( List.of(testPackage1With2WidgetsListEntry, testPackage2With2WidgetsListEntry)); // WHEN the adapter is updated with the same list of apps but test package 2 has 3 widgets // now. WidgetsListBaseEntry testPackage1With3WidgetsListEntry = generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2); mAdapter.setWidgets( List.of(testPackage1With2WidgetsListEntry, testPackage1With3WidgetsListEntry)); // THEN the onItemRangeChanged is invoked. verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull()); } @Test public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception { // TODO: insert and remove combined. curMap // newMap [A, C, D] [A, B, E] // B - C < 0, removed B from index 1 [A, E] // E - C > 0, C inserted to index 1 [A, C, E] // E - D > 0, D inserted to index 2 [A, C, D, E] // E - null = -1, E deleted from index 3 [A, C, D] public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() { List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5); // GIVEN the current widgets list consist of [A, B, E]. List<WidgetsListBaseEntry> currentList = List.of( allAppsWithWidgets.get(0), allAppsWithWidgets.get(1), allAppsWithWidgets.get(4)); mAdapter.setWidgets(currentList); // WHEN the widgets list is updated to [A, C, D]. List<WidgetsListBaseEntry> newList = List.of( allAppsWithWidgets.get(0), allAppsWithWidgets.get(2), allAppsWithWidgets.get(3)); mAdapter.setWidgets(newList); // Computation logic | [Intermediate list during computation] // THEN B <> C < 0, removed B from index 1 | [A, E] verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1); // THEN E <> C > 0, C inserted to index 1 | [A, C, E] verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1); // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E] verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1); // THEN E <> null = -1, E deleted from index 3 | [A, C, D] verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1); } /** * Helper method to generate the sample widget model map that can be used for the tests * @param num the number of WidgetItem the map should contain */ private ArrayList<WidgetListRowEntry> generateSampleMap(int num) { ArrayList<WidgetListRowEntry> result = new ArrayList<>(); private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) { ArrayList<WidgetsListBaseEntry> result = new ArrayList<>(); if (num <= 0) return result; ShadowPackageManager spm = shadowOf(mContext.getPackageManager()); for (int i = 0; i < num; i++) { ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet"); AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo(); widgetInfo.provider = cn; ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn)); String packageName = "com.placeholder.apk" + i; WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache); List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1); PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); PackageItemInfo pInfo = new PackageItemInfo(packageName); pInfo.title = pInfo.packageName; pInfo.user = wi.user; pInfo.user = widgetItems.get(0).user; pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi)))); result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems)); } return result; } private WidgetsListBaseEntry generateSampleAppWithWidgets(String packageName, int numOfWidgets) { PackageItemInfo appInfo = new PackageItemInfo(packageName); appInfo.title = appInfo.packageName; appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); return new WidgetsListContentEntry(appInfo, /* titleSectionName= */ "", generateWidgetItems(packageName, numOfWidgets)); } private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) { ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager()); ArrayList<WidgetItem> widgetItems = new ArrayList<>(); for (int i = 0; i < numOfWidgets; i++) { ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i); AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo(); widgetInfo.provider = cn; ReflectionHelpers.setField(widgetInfo, "providerInfo", packageManager.addReceiverIfNotPresent(cn)); widgetItems.add(new WidgetItem( LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache)); } return widgetItems; } }