blob: 9bea2fb4061c2c3687189deb910d938931e97425 [file] [log] [blame]
/*
* 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.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.doAnswer;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;
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;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
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;
@Mock private RecyclerView.AdapterDataObserver mListener;
@Mock private IconCache mIconCache;
private WidgetsListAdapter mAdapter;
private InvariantDeviceProfile mTestProfile;
private Context mContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
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 setWidgets_shouldNotifyDataSetChanged() {
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener).onChanged();
}
@Test
public void setWidgets_withItemInserted_shouldNotifyItemInserted() {
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(2));
verify(mListener).onItemRangeInserted(eq(1), eq(1));
}
@Test
public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
mAdapter.setWidgets(generateSampleMap(2));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener).onItemRangeRemoved(eq(1), eq(1));
}
@Test
public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull());
}
@Test
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 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<WidgetsListBaseEntry> generateSampleMap(int num) {
ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
if (num <= 0) return result;
for (int i = 0; i < num; i++) {
String packageName = "com.placeholder.apk" + i;
List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1);
PackageItemInfo pInfo = new PackageItemInfo(packageName);
pInfo.title = pInfo.packageName;
pInfo.user = widgetItems.get(0).user;
pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
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;
}
}