Merge "Moving all widget picker tests to instrumentation tests" into sc-v2-dev

This commit is contained in:
Sunny Goyal
2021-08-19 01:41:40 +00:00
committed by Android (Google) Code Review
18 changed files with 251 additions and 234 deletions
@@ -0,0 +1,62 @@
/*
* 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.util;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.ContextThemeWrapper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
/**
* {@link ContextWrapper} with internal Launcher interface for testing
*/
public class ActivityContextWrapper extends ContextThemeWrapper implements ActivityContext {
private final DeviceProfile mProfile;
private final MyDragLayer mMyDragLayer;
public ActivityContextWrapper(Context base) {
super(base, android.R.style.Theme_DeviceDefault);
mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base);
mMyDragLayer = new MyDragLayer(this);
}
@Override
public BaseDragLayer getDragLayer() {
return mMyDragLayer;
}
@Override
public DeviceProfile getDeviceProfile() {
return mProfile;
}
private static class MyDragLayer extends BaseDragLayer<ActivityContextWrapper> {
MyDragLayer(Context context) {
super(context, null, 1);
}
@Override
public void recreateControllers() {
mControllers = new TouchController[0];
}
}
}
@@ -15,12 +15,19 @@
*/
package com.android.launcher3.util;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.os.Process;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.ItemInfo;
@@ -101,4 +108,17 @@ public class WidgetUtils {
resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
writer.getValues(targetContext));
}
/**
* Creates a {@link AppWidgetProviderInfo} for the provided component name
*/
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
AppWidgetProviderInfo info = AppWidgetManager.getInstance(getApplicationContext())
.getInstalledProvidersForPackage(
getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
.get(0);
info.provider = cn;
return info;
}
}
@@ -0,0 +1,275 @@
/*
* 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.widget;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class LauncherAppWidgetProviderInfoTest {
private static final int CELL_SIZE = 50;
private static final int NUM_OF_COLS = 4;
private static final int NUM_OF_ROWS = 5;
private Context mContext;
@Before
public void setUp() {
mContext = getApplicationContext();
}
@Test
public void initSpans_minWidthSmallerThanCellWidth_shouldInitializeSpansToOne() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 20;
info.minHeight = 20;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.spanX).isEqualTo(1);
assertThat(info.spanY).isEqualTo(1);
}
@Test
public void initSpans_minWidthLargerThanCellWidth_shouldInitializeSpans() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 80;
info.minHeight = 80;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.spanX).isEqualTo(2);
assertThat(info.spanY).isEqualTo(2);
}
@Test
public void
initSpans_minWidthLargerThanGridColumns_shouldInitializeSpansToAtMostTheGridColumns() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = CELL_SIZE * (NUM_OF_COLS + 1);
info.minHeight = 20;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.spanX).isEqualTo(NUM_OF_COLS);
assertThat(info.spanY).isEqualTo(1);
}
@Test
public void initSpans_minHeightLargerThanGridRows_shouldInitializeSpansToAtMostTheGridRows() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 20;
info.minHeight = 50 * (NUM_OF_ROWS + 1);
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.spanX).isEqualTo(1);
assertThat(info.spanY).isEqualTo(NUM_OF_ROWS);
}
@Test
public void initSpans_minResizeWidthUnspecified_shouldInitializeMinSpansToOne() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(1);
assertThat(info.minSpanY).isEqualTo(1);
}
@Test
public void initSpans_minResizeWidthSmallerThanCellWidth_shouldInitializeMinSpansToOne() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 100;
info.minHeight = 100;
info.minResizeWidth = 20;
info.minResizeHeight = 20;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(1);
assertThat(info.minSpanY).isEqualTo(1);
}
@Test
public void initSpans_minResizeWidthLargerThanCellWidth_shouldInitializeMinSpans() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 100;
info.minHeight = 100;
info.minResizeWidth = 80;
info.minResizeHeight = 80;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(2);
assertThat(info.minSpanY).isEqualTo(2);
}
@Test
public void initSpans_minResizeWidthWithCellSpacingAndWidgetInset_shouldInitializeMinSpans() {
InvariantDeviceProfile idp = createIDP();
DeviceProfile dp = idp.supportedProfiles.get(0);
Rect padding = new Rect();
AppWidgetHostView.getDefaultPaddingForWidget(mContext, null, padding);
int maxPadding = Math.max(Math.max(padding.left, padding.right),
Math.max(padding.top, padding.bottom));
dp.cellLayoutBorderSpacingPx = maxPadding + 1;
Mockito.when(dp.shouldInsetWidgets()).thenReturn(true);
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = CELL_SIZE * 3;
info.minHeight = CELL_SIZE * 3;
info.minResizeWidth = CELL_SIZE * 2 + maxPadding;
info.minResizeHeight = CELL_SIZE * 2 + maxPadding;
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(2);
assertThat(info.minSpanY).isEqualTo(2);
}
@Test
public void initSpans_minResizeWidthWithCellSpacingAndNoWidgetInset_shouldInitializeMinSpans() {
InvariantDeviceProfile idp = createIDP();
DeviceProfile dp = idp.supportedProfiles.get(0);
Rect padding = new Rect();
AppWidgetHostView.getDefaultPaddingForWidget(mContext, null, padding);
int maxPadding = Math.max(Math.max(padding.left, padding.right),
Math.max(padding.top, padding.bottom));
dp.cellLayoutBorderSpacingPx = maxPadding - 1;
Mockito.when(dp.shouldInsetWidgets()).thenReturn(false);
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = CELL_SIZE * 3;
info.minHeight = CELL_SIZE * 3;
info.minResizeWidth = CELL_SIZE * 2 + maxPadding;
info.minResizeHeight = CELL_SIZE * 2 + maxPadding;
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(3);
assertThat(info.minSpanY).isEqualTo(3);
}
@Test
public void
initSpans_minResizeWidthHeightLargerThanMinWidth_shouldUseMinWidthHeightAsMinSpans() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 20;
info.minHeight = 20;
info.minResizeWidth = 80;
info.minResizeHeight = 80;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.minSpanX).isEqualTo(1);
assertThat(info.minSpanY).isEqualTo(1);
}
@Test
public void isMinSizeFulfilled_minWidthAndHeightWithinGridSize_shouldReturnTrue() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 80;
info.minHeight = 80;
info.minResizeWidth = 50;
info.minResizeHeight = 50;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.isMinSizeFulfilled()).isTrue();
}
@Test
public void
isMinSizeFulfilled_minWidthAndMinResizeWidthExceededGridColumns_shouldReturnFalse() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = CELL_SIZE * (NUM_OF_COLS + 2);
info.minHeight = 80;
info.minResizeWidth = CELL_SIZE * (NUM_OF_COLS + 1);
info.minResizeHeight = 50;
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.isMinSizeFulfilled()).isFalse();
}
@Test
public void isMinSizeFulfilled_minHeightAndMinResizeHeightExceededGridRows_shouldReturnFalse() {
LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
info.minWidth = 80;
info.minHeight = CELL_SIZE * (NUM_OF_ROWS + 2);
info.minResizeWidth = 50;
info.minResizeHeight = CELL_SIZE * (NUM_OF_ROWS + 1);
InvariantDeviceProfile idp = createIDP();
info.initSpans(mContext, idp);
assertThat(info.isMinSizeFulfilled()).isFalse();
}
private InvariantDeviceProfile createIDP() {
DeviceProfile profile = Mockito.mock(DeviceProfile.class);
doAnswer(i -> {
((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
return null;
}).when(profile).getCellSize(any(Point.class));
Mockito.when(profile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
Mockito.when(profile.shouldInsetWidgets()).thenReturn(true);
InvariantDeviceProfile idp = new InvariantDeviceProfile();
List<DeviceProfile> supportedProfiles = new ArrayList<>(idp.supportedProfiles);
supportedProfiles.add(profile);
idp.supportedProfiles = Collections.unmodifiableList(supportedProfiles);
idp.numColumns = NUM_OF_COLS;
idp.numRows = NUM_OF_ROWS;
return idp;
}
}
@@ -0,0 +1,311 @@
/*
* 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.widget.picker;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.UserHandle;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
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.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsDiffReporterTest {
private static final String TEST_PACKAGE_PREFIX = "com.android.test";
private static final WidgetListBaseRowEntryComparator COMPARATOR =
new WidgetListBaseRowEntryComparator();
@Mock private IconCache mIconCache;
@Mock private RecyclerView.Adapter mAdapter;
private InvariantDeviceProfile mTestProfile;
private WidgetsDiffReporter mWidgetsDiffReporter;
private Context mContext;
private WidgetsListHeaderEntry mHeaderA;
private WidgetsListHeaderEntry mHeaderB;
private WidgetsListHeaderEntry mHeaderC;
private WidgetsListHeaderEntry mHeaderD;
private WidgetsListHeaderEntry mHeaderE;
private WidgetsListContentEntry mContentC;
private WidgetsListContentEntry mContentE;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
.getComponent().getPackageName())
.when(mIconCache).getTitleNoCache(any());
mContext = getApplicationContext();
mWidgetsDiffReporter = new WidgetsDiffReporter(mIconCache, mAdapter);
mHeaderA = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A",
/* appName= */ "A", /* numOfWidgets= */ 3);
mHeaderB = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B",
/* appName= */ "B", /* numOfWidgets= */ 3);
mHeaderC = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C",
/* appName= */ "C", /* numOfWidgets= */ 3);
mContentC = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "C",
/* appName= */ "C", /* numOfWidgets= */ 3);
mHeaderD = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "D",
/* appName= */ "D", /* numOfWidgets= */ 3);
mHeaderE = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "E",
/* appName= */ "E", /* numOfWidgets= */ 3);
mContentE = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "E",
/* appName= */ "E", /* numOfWidgets= */ 3);
}
@Test
public void listNotChanged_shouldNotInvokeAnyCallbacks() {
// GIVEN the current list has app headers [A, B, C].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mHeaderC));
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, currentList, COMPARATOR);
// THEN there is no adaptor callback.
verifyZeroInteractions(mAdapter);
// THEN the current list contains the same entries.
assertThat(currentList).containsExactly(mHeaderA, mHeaderB, mHeaderC);
}
@Test
public void headersOnly_emptyListToNonEmpty_shouldInvokeNotifyDataSetChanged() {
// GIVEN the current list has app headers [A, B, C].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>();
List<WidgetsListBaseEntry> newList = List.of(
createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A", "A", 3),
createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B", "B", 3),
createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C", "C", 3));
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN notifyDataSetChanged is called
verify(mAdapter).notifyDataSetChanged();
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
@Test
public void headersOnly_nonEmptyToEmptyList_shouldInvokeNotifyDataSetChanged() {
// GIVEN the current list has app headers [A, B, C].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mHeaderC));
// GIVEN the new list is empty.
List<WidgetsListBaseEntry> newList = List.of();
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN notifyDataSetChanged is called.
verify(mAdapter).notifyDataSetChanged();
// THEN the current list isEmpty.
assertThat(currentList).isEmpty();
}
@Test
public void headersOnly_itemAddedAndRemovedInTheNewList_shouldInvokeCorrectCallbacks() {
// GIVEN the current list has app headers [A, B, D].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mHeaderD));
// GIVEN the new list has app headers [A, C, E].
List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderC, mHeaderE);
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN "B" is removed from position 1.
verify(mAdapter).notifyItemRemoved(/* position= */ 1);
// THEN "D" is removed from position 2.
verify(mAdapter).notifyItemRemoved(/* position= */ 2);
// THEN "C" is inserted at position 1.
verify(mAdapter).notifyItemInserted(/* position= */ 1);
// THEN "E" is inserted at position 2.
verify(mAdapter).notifyItemInserted(/* position= */ 2);
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
@Test
public void headersContentsMix_itemAddedAndRemovedInTheNewList_shouldInvokeCorrectCallbacks() {
// GIVEN the current list has app headers [A, B, E content].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mContentE));
// GIVEN the new list has app headers [A, C content, D].
List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mContentC, mHeaderD);
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN "B" is removed from position 1.
verify(mAdapter).notifyItemRemoved(/* position= */ 1);
// THEN "C content" is inserted at position 1.
verify(mAdapter).notifyItemInserted(/* position= */ 1);
// THEN "D" is inserted at position 2.
verify(mAdapter).notifyItemInserted(/* position= */ 2);
// THEN "E content" is removed from position 3.
verify(mAdapter).notifyItemRemoved(/* position= */ 3);
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
@Test
public void headersContentsMix_userInteractWithHeader_shouldInvokeCorrectCallbacks() {
// GIVEN the current list has app headers [A, B, E content].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mContentE));
// GIVEN the new list has app headers [A, B, E content] and the user has interacted with B.
List<WidgetsListBaseEntry> newList =
List.of(mHeaderA, mHeaderB.withWidgetListShown(), mContentE);
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN notify "B" has been changed.
verify(mAdapter).notifyItemChanged(/* position= */ 1);
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
@Test
public void headersContentsMix_headerWidgetsModified_shouldInvokeCorrectCallbacks() {
// GIVEN the current list has app headers [A, B, E content].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mContentE));
// GIVEN the new list has one of the headers widgets list modified.
List<WidgetsListBaseEntry> newList = List.of(
new WidgetsListHeaderEntry(
mHeaderA.mPkgItem, mHeaderA.mTitleSectionName,
mHeaderA.mWidgets.subList(0, 1)),
mHeaderB, mContentE);
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN notify "A" has been changed.
verify(mAdapter).notifyItemChanged(/* position= */ 0);
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
@Test
public void headersContentsMix_contentMaxSpanSizeModified_shouldInvokeCorrectCallbacks() {
// GIVEN the current list has app headers [A, B, E content].
ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
List.of(mHeaderA, mHeaderB, mContentE));
// GIVEN the new list has max span size in "E content" modified.
List<WidgetsListBaseEntry> newList = List.of(
mHeaderA,
mHeaderB,
new WidgetsListContentEntry(
mContentE.mPkgItem,
mContentE.mTitleSectionName,
mContentE.mWidgets,
mContentE.getMaxSpanSizeInCells() + 1));
// WHEN computing the list difference.
mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
// THEN notify "E content" has been changed.
verify(mAdapter).notifyItemChanged(/* position= */ 2);
// THEN the current list contains all elements from the new list.
assertThat(currentList).containsExactlyElementsIn(newList);
}
private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
int numOfWidgets) {
List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
widgetItems.get(0).user);
return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
}
private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
int numOfWidgets) {
List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
widgetItems.get(0).user);
return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
}
private PackageItemInfo createPackageItemInfo(String packageName, String appName,
UserHandle userHandle) {
PackageItemInfo pInfo = new PackageItemInfo(packageName);
pInfo.title = appName;
pInfo.user = userHandle;
pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return pInfo;
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(cn);
WidgetItem widgetItem = new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache);
widgetItems.add(widgetItem);
}
return widgetItems;
}
}
@@ -0,0 +1,284 @@
/*
* 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 androidx.test.core.app.ApplicationProvider.getApplicationContext;
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 android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Process;
import android.os.UserHandle;
import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
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.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
/**
* Unit tests for WidgetsListAdapter
* Note that all indices matching are shifted by 1 to account for the empty space at the start.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsListAdapterTest {
private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
@Mock private LayoutInflater mMockLayoutInflater;
@Mock private RecyclerView.AdapterDataObserver mListener;
@Mock private IconCache mIconCache;
private WidgetsListAdapter mAdapter;
private InvariantDeviceProfile mTestProfile;
private UserHandle mUserHandle;
private Context mContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mUserHandle = Process.myUserHandle();
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
mIconCache, () -> 0, 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(2), eq(1));
}
@Test
public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
mAdapter.setWidgets(generateSampleMap(2));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener).onItemRangeRemoved(eq(2), eq(1));
}
@Test
public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(1));
verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
}
@Test
public void headerClick_expanded_shouldNotifyItemChange() {
// GIVEN a list of widgets entries:
// [com.google.test0, com.google.test0 content,
// com.google.test1, com.google.test1 content,
// com.google.test2, com.google.test2 content]
// The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
mAdapter.setWidgets(generateSampleMap(3));
// WHEN com.google.test.1 header is expanded.
mAdapter.onHeaderClicked(/* showWidgets= */ true,
new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
// THEN the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
// com.google.test.1 content is inserted into position 2.
verify(mListener).onItemRangeInserted(eq(3), eq(1));
}
@Test
public void setWidgets_expandedApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() {
// GIVEN the adapter was first populated with com.google.test0 & com.google.test1. Each app
// has one widget.
ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
mAdapter.setWidgets(allEntries);
// GIVEN test com.google.test1 is expanded.
// Visible entries in the adapter are:
// [com.google.test0, com.google.test1, com.google.test1 content]
mAdapter.onHeaderClicked(/* showWidgets= */ true,
new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
Mockito.reset(mListener);
// WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
// now.
WidgetsListContentEntry testPackage1ContentEntry =
(WidgetsListContentEntry) allEntries.get(3);
WidgetItem widgetItem = testPackage1ContentEntry.mWidgets.get(0);
WidgetsListContentEntry newTestPackage1ContentEntry = new WidgetsListContentEntry(
testPackage1ContentEntry.mPkgItem,
testPackage1ContentEntry.mTitleSectionName, List.of(widgetItem, widgetItem));
allEntries.set(3, newTestPackage1ContentEntry);
mAdapter.setWidgets(allEntries);
// THEN the onItemRangeChanged is invoked for "com.google.test1 content" at index 2.
verify(mListener).onItemRangeChanged(eq(3), eq(1), isNull());
}
@Test
public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() {
// GIVEN a widgets entry list:
// Index: 0| 1 | 2| 3 | 4| 5 | 6| 7 | 8| 9 |
// [A, A content, B, B content, C, C content, D, D content, E, E content]
List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5);
// GIVEN the current widgets list consist of [A, A content, B, B content, E, E content].
// GIVEN the visible widgets list consist of [A, B, E]
List<WidgetsListBaseEntry> currentList = List.of(
// A & A content
allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
// B & B content
allAppsWithWidgets.get(2), allAppsWithWidgets.get(3),
// E & E content
allAppsWithWidgets.get(8), allAppsWithWidgets.get(9));
mAdapter.setWidgets(currentList);
// WHEN the widgets list is updated to [A, A content, C, C content, D, D content].
// WHEN the visible widgets list is updated to [A, C, D].
List<WidgetsListBaseEntry> newList = List.of(
// A & A content
allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
// C & C content
allAppsWithWidgets.get(4), allAppsWithWidgets.get(5),
// D & D content
allAppsWithWidgets.get(6), allAppsWithWidgets.get(7));
mAdapter.setWidgets(newList);
// Account for 1st items as empty space
// Computation logic | [Intermediate list during computation]
// THEN B <> C < 0, removed B from index 1 | [A, E]
verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
// THEN E <> C > 0, C inserted to index 1 | [A, C, E]
verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
// THEN E <> D > 0, D inserted to index 2 | [A, C, D, E]
verify(mListener).onItemRangeInserted(/* positionStart= */ 3, /* itemCount= */ 1);
// THEN E <> null = -1, E deleted from index 3 | [A, C, D]
verify(mListener).onItemRangeRemoved(/* positionStart= */ 4, /* itemCount= */ 1);
}
@Test
public void setWidgetsOnSearch_expandedApp_shouldResetExpandedApp() {
// GIVEN a list of widgets entries:
// [com.google.test0, com.google.test0 content,
// com.google.test1, com.google.test1 content,
// com.google.test2, com.google.test2 content]
// The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
mAdapter.setWidgetsOnSearch(allEntries);
// GIVEN com.google.test.1 header is expanded. The visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
mAdapter.onHeaderClicked(/* showWidgets= */ true,
new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
Mockito.reset(mListener);
// WHEN same widget entries are set again.
mAdapter.setWidgetsOnSearch(allEntries);
// THEN expanded app is reset and the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test2]
verify(mListener).onItemRangeChanged(eq(2), eq(1), isNull());
verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
}
/**
* Generates a list of sample widget entries.
*
* <p>Each sample app has 1 widget only. An app is represented by 2 entries,
* {@link WidgetsListHeaderEntry} & {@link WidgetsListContentEntry}. Only
* {@link WidgetsListHeaderEntry} is always visible in the {@link WidgetsListAdapter}.
* {@link WidgetsListContentEntry} is only shown upon clicking the corresponding app's
* {@link WidgetsListHeaderEntry}. Only at most one {@link WidgetsListContentEntry} is shown at
* a time.
*
* @param num the number of apps that have widgets.
*/
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 = TEST_PACKAGE_PLACEHOLDER + 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 WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems));
result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems));
}
return result;
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache));
}
return widgetItems;
}
}
@@ -0,0 +1,153 @@
/*
* 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.widget.picker;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
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.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsListHeaderViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
private Context mContext;
private WidgetsListHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
@Mock
private IconCache mIconCache;
@Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
LayoutInflater.from(mContext),
mOnHeaderClickListener,
new WidgetsListDrawableFactory(mContext));
}
@Test
public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListHeaderEntry entry = generateSampleAppHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
assertThat(appTitle.getText()).isEqualTo(APP_NAME);
assertThat(appSubtitle.getText()).isEqualTo("3 widgets");
}
@Test
public void bindViewHolder_shouldAttachOnHeaderClickListener() {
WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListHeaderEntry entry = generateSampleAppHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
}
private WidgetsListHeaderEntry generateSampleAppHeader(String appName, String packageName,
int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
appInfo.title = appName;
appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return new WidgetsListHeaderEntry(appInfo,
/* titleSectionName= */ "",
generateWidgetItems(packageName, numOfWidgets));
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache));
}
return widgetItems;
}
}
@@ -0,0 +1,153 @@
/*
* 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.widget.picker;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
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.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsListSearchHeaderViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
private Context mContext;
private WidgetsListSearchHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
@Mock
private IconCache mIconCache;
@Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
LayoutInflater.from(mContext),
mOnHeaderClickListener,
new WidgetsListDrawableFactory(mContext));
}
@Test
public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
assertThat(appTitle.getText()).isEqualTo(APP_NAME);
assertThat(appSubtitle.getText())
.isEqualTo(".SampleWidget0, .SampleWidget1, .SampleWidget2");
}
@Test
public void bindViewHolder_shouldAttachOnHeaderClickListener() {
WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
}
private WidgetsListSearchHeaderEntry generateSampleSearchHeader(String appName,
String packageName, int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
appInfo.title = appName;
appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return new WidgetsListSearchHeaderEntry(appInfo,
/* titleSectionName= */ "",
generateWidgetItems(packageName, numOfWidgets));
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache));
}
return widgetItems;
}
}
@@ -0,0 +1,156 @@
/*
* 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.widget.picker;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.FrameLayout;
import android.widget.TableRow;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
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.util.ActivityContextWrapper;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
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 java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsListTableViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
private Context mContext;
private WidgetsListTableViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
@Mock
private OnLongClickListener mOnLongClickListener;
@Mock
private OnClickListener mOnIconClickListener;
@Mock
private IconCache mIconCache;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
LayoutInflater.from(mContext),
mOnIconClickListener,
mOnLongClickListener,
new WidgetsListDrawableFactory(mContext));
}
@Test
public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() throws Exception {
WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mContext));
WidgetsListContentEntry entry = generateSampleAppWithWidgets(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
Executors.MAIN_EXECUTOR.submit(() -> { }).get();
// THEN the table container has one row, which contains 3 widgets.
// View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
assertThat(viewHolder.tableContainer.getChildCount()).isEqualTo(1);
TableRow row = (TableRow) viewHolder.tableContainer.getChildAt(0);
assertThat(row.getChildCount()).isEqualTo(3);
// Widget 0 label is .SampleWidget0.
assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
// Widget 1 label is .SampleWidget1.
assertWidgetCellWithLabel(row.getChildAt(1), ".SampleWidget1");
// Widget 2 label is .SampleWidget2.
assertWidgetCellWithLabel(row.getChildAt(2), ".SampleWidget2");
}
private WidgetsListContentEntry generateSampleAppWithWidgets(String appName, String packageName,
int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
appInfo.title = appName;
appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return new WidgetsListContentEntry(appInfo,
/* titleSectionName= */ "",
generateWidgetItems(packageName, numOfWidgets),
Integer.MAX_VALUE);
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache));
}
return widgetItems;
}
private void assertWidgetCellWithLabel(View view, String label) {
assertThat(view).isInstanceOf(WidgetCell.class);
TextView widgetLabel = (TextView) view.findViewById(R.id.widget_name);
assertThat(widgetLabel.getText()).isEqualTo(label);
}
}
@@ -0,0 +1,257 @@
/*
* 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.widget.picker.model;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
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.LauncherAppWidgetProviderInfo;
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 java.util.HashMap;
import java.util.List;
import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsListContentEntryTest {
private static final String PACKAGE_NAME = "com.android.test";
private static final String PACKAGE_NAME_2 = "com.android.test2";
private final PackageItemInfo mPackageItemInfo1 = new PackageItemInfo(PACKAGE_NAME);
private final PackageItemInfo mPackageItemInfo2 = new PackageItemInfo(PACKAGE_NAME_2);
private final ComponentName mWidget1 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget1");
private final ComponentName mWidget2 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget2");
private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3");
private final Map<ComponentName, String> mWidgetsToLabels = new HashMap();
@Mock private IconCache mIconCache;
private InvariantDeviceProfile mTestProfile;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mWidgetsToLabels.put(mWidget1, "Cat");
mWidgetsToLabels.put(mWidget2, "Dog");
mWidgetsToLabels.put(mWidget3, "Bird");
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return mWidgetsToLabels.get(componentWithLabel.getComponent());
}).when(mIconCache).getTitleNoCache(any());
}
@Test
public void unsortedWidgets_diffLabels_shouldSortWidgetItems() {
// GIVEN a list of widgets in unsorted order.
// Cat 2x3
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
// Dog 2x3
WidgetItem widgetItem2 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 3);
// Bird 2x3
WidgetItem widgetItem3 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 3);
// WHEN creates a WidgetsListRowEntry with the unsorted widgets.
WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1, widgetItem2, widgetItem3));
// THEN the widgets list is sorted by their labels alphabetically: [Bird, Cat, Dog].
assertThat(widgetsListRowEntry.mWidgets)
.containsExactly(widgetItem3, widgetItem1, widgetItem2)
.inOrder();
assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1);
}
@Test
public void unsortedWidgets_sameLabels_differentSize_shouldSortWidgetItems() {
// GIVEN a list of widgets in unsorted order.
// Cat 3x3
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 3, /* spanY= */ 3);
// Cat 1x2
WidgetItem widgetItem2 = createWidgetItem(mWidget1, /* spanX= */ 1, /* spanY= */ 2);
// Cat 2x2
WidgetItem widgetItem3 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 2);
// WHEN creates a WidgetsListRowEntry with the unsorted widgets.
WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1, widgetItem2, widgetItem3));
// THEN the widgets list is sorted by their gird sizes in an ascending order:
// [1x2, 2x2, 3x3].
assertThat(widgetsListRowEntry.mWidgets)
.containsExactly(widgetItem2, widgetItem3, widgetItem1)
.inOrder();
assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1);
}
@Test
public void unsortedWidgets_hodgepodge_shouldSortWidgetItems() {
// GIVEN a list of widgets in unsorted order.
// Cat 3x3
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 3, /* spanY= */ 3);
// Cat 1x2
WidgetItem widgetItem2 = createWidgetItem(mWidget1, /* spanX= */ 1, /* spanY= */ 2);
// Dog 2x2
WidgetItem widgetItem3 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 2);
// Bird 2x2
WidgetItem widgetItem4 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 2);
// WHEN creates a WidgetsListRowEntry with the unsorted widgets.
WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1, widgetItem2, widgetItem3, widgetItem4));
// THEN the widgets list is first sorted by labels alphabetically. Then, for widgets with
// same labels, they are sorted by their gird sizes in an ascending order:
// [Bird 2x2, Cat 1x2, Cat 3x3, Dog 2x2]
assertThat(widgetsListRowEntry.mWidgets)
.containsExactly(widgetItem4, widgetItem2, widgetItem1, widgetItem3)
.inOrder();
assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo1);
}
@Test
public void equals_entriesWithDifferentPackageItemInfo_returnFalse() {
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry(
mPackageItemInfo2,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse();
}
@Test
public void equals_entriesWithDifferentTitleSectionName_returnFalse() {
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "S",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse();
}
@Test
public void equals_entriesWithDifferentWidgetsList_returnFalse() {
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
WidgetItem widgetItem2 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 3);
WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem2),
/* maxSpanSizeInCells= */ 3);
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse();
}
@Test
public void equals_entriesWithDifferentMaxSpanSize_returnFalse() {
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 2);
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isFalse();
}
@Test
public void equals_entriesWithSameContents_returnTrue() {
WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
WidgetsListContentEntry widgetsListRowEntry1 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
WidgetsListContentEntry widgetsListRowEntry2 = new WidgetsListContentEntry(
mPackageItemInfo1,
/* titleSectionName= */ "T",
List.of(widgetItem1),
/* maxSpanSizeInCells= */ 3);
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isTrue();
}
private WidgetItem createWidgetItem(ComponentName componentName, int spanX, int spanY) {
String label = mWidgetsToLabels.get(componentName);
AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(componentName);
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(getApplicationContext(), widgetInfo);
launcherAppWidgetProviderInfo.spanX = spanX;
launcherAppWidgetProviderInfo.spanY = spanY;
launcherAppWidgetProviderInfo.label = label;
return new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache);
}
}
@@ -0,0 +1,212 @@
/*
* 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.widget.picker.search;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.UserHandle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
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.popup.PopupDataProvider;
import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SimpleWidgetsSearchAlgorithmTest {
@Mock private IconCache mIconCache;
private InvariantDeviceProfile mTestProfile;
private WidgetsListHeaderEntry mCalendarHeaderEntry;
private WidgetsListContentEntry mCalendarContentEntry;
private WidgetsListHeaderEntry mCameraHeaderEntry;
private WidgetsListContentEntry mCameraContentEntry;
private WidgetsListHeaderEntry mClockHeaderEntry;
private WidgetsListContentEntry mClockContentEntry;
private Context mContext;
private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
@Mock
private PopupDataProvider mDataProvider;
@Mock
private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mContext = getApplicationContext();
mCalendarHeaderEntry =
createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
mCalendarContentEntry =
createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 11);
mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 11);
mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
mSimpleWidgetsSearchAlgorithm = MAIN_EXECUTOR.submit(
() -> new SimpleWidgetsSearchAlgorithm(mDataProvider)).get();
doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
}
@Test
public void filter_shouldMatchOnAppName() {
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
.when(mDataProvider)
.getAllWidgets();
assertEquals(List.of(
new WidgetsListSearchHeaderEntry(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets),
mCalendarContentEntry,
new WidgetsListSearchHeaderEntry(
mCameraHeaderEntry.mPkgItem,
mCameraHeaderEntry.mTitleSectionName,
mCameraHeaderEntry.mWidgets),
mCameraContentEntry),
SimpleWidgetsSearchAlgorithm.getFilteredWidgets(mDataProvider, "Ca"));
}
@Test
public void filter_shouldMatchOnWidgetLabel() {
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry))
.when(mDataProvider)
.getAllWidgets();
assertEquals(List.of(
new WidgetsListSearchHeaderEntry(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets.subList(1, 2)),
new WidgetsListContentEntry(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets.subList(1, 2)),
new WidgetsListSearchHeaderEntry(
mCameraHeaderEntry.mPkgItem,
mCameraHeaderEntry.mTitleSectionName,
mCameraHeaderEntry.mWidgets.subList(1, 3)),
new WidgetsListContentEntry(
mCameraHeaderEntry.mPkgItem,
mCameraHeaderEntry.mTitleSectionName,
mCameraHeaderEntry.mWidgets.subList(1, 3))),
SimpleWidgetsSearchAlgorithm.getFilteredWidgets(mDataProvider, "Widget1"));
}
@Test
public void doSearch_shouldInformCallback() throws Exception {
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
.when(mDataProvider)
.getAllWidgets();
mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
MAIN_EXECUTOR.submit(() -> { }).get();
verify(mSearchCallback).onSearchResult(
matches("Ca"), argThat(a -> a != null && !a.isEmpty()));
}
private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
int numOfWidgets) {
List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
widgetItems.get(0).user);
return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
}
private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
int numOfWidgets) {
List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
widgetItems.get(0).user);
return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
}
private PackageItemInfo createPackageItemInfo(String packageName, String appName,
UserHandle userHandle) {
PackageItemInfo pInfo = new PackageItemInfo(packageName);
pInfo.title = appName;
pInfo.user = userHandle;
pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return pInfo;
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(cn);
WidgetItem widgetItem = new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
mTestProfile, mIconCache);
widgetItems.add(widgetItem);
}
return widgetItems;
}
}
@@ -0,0 +1,143 @@
/*
* 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.widget.picker.search;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import android.view.View;
import android.widget.ImageButton;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WidgetsSearchBarControllerTest {
private WidgetsSearchBarController mController;
private ExtendedEditText mEditText;
private ImageButton mCancelButton;
@Mock
private SearchModeListener mSearchModeListener;
@Mock
private SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = getApplicationContext();
mEditText = new ExtendedEditText(context);
mCancelButton = new ImageButton(context);
mController = new WidgetsSearchBarController(
mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
}
@Test
public void onSearchResult_shouldInformSearchModeListener() {
ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
mController.onSearchResult("abc", entries);
verify(mSearchModeListener).onSearchResults(entries);
}
@Test
public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
mEditText.setText("abc");
verify(mSearchModeListener).enterSearchMode();
verifyNoMoreInteractions(mSearchModeListener);
}
@Test
public void afterTextChanged_shouldDoSearch() {
mEditText.setText("abc");
verify(mSearchAlgorithm).doSearch(eq("abc"), any());
}
@Test
public void afterTextChanged_shouldShowCancelButton() {
mEditText.setText("abc");
assertEquals(mCancelButton.getVisibility(), View.VISIBLE);
}
@Test
public void afterTextChanged_empty_shouldInformSearchModeListenerToExitSearch() {
mEditText.setText("");
verify(mSearchModeListener).exitSearchMode();
verifyNoMoreInteractions(mSearchModeListener);
}
@Test
public void afterTextChanged_empty_shouldCancelSearch() {
mEditText.setText("");
verify(mSearchAlgorithm).cancel(true);
verifyNoMoreInteractions(mSearchAlgorithm);
}
@Test
public void afterTextChanged_empty_shouldHideCancelButton() {
mEditText.setText("");
assertEquals(mCancelButton.getVisibility(), View.GONE);
}
@Test
public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() {
mCancelButton.performClick();
verify(mSearchModeListener).exitSearchMode();
}
@Test
public void cancelSearch_shouldCancelSearch() {
mCancelButton.performClick();
verify(mSearchAlgorithm).cancel(true);
verifyNoMoreInteractions(mSearchAlgorithm);
}
@Test
public void cancelSearch_shouldClearSearchBar() {
mCancelButton.performClick();
assertEquals(mEditText.getText().toString(), "");
}
}
@@ -0,0 +1,206 @@
/*
* 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.widget.picker.util;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.util.WidgetsTableUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WidgetsTableUtilsTest {
private static final String TEST_PACKAGE = "com.google.test";
@Mock
private IconCache mIconCache;
private Context mContext;
private InvariantDeviceProfile mTestProfile;
private WidgetItem mWidget1x1;
private WidgetItem mWidget2x2;
private WidgetItem mWidget2x3;
private WidgetItem mWidget2x4;
private WidgetItem mWidget4x4;
private WidgetItem mShortcut1;
private WidgetItem mShortcut2;
private WidgetItem mShortcut3;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = getApplicationContext();
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
initTestWidgets();
initTestShortcuts();
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
.getComponent().getPackageName())
.when(mIconCache).getTitleNoCache(any());
}
@Test
public void groupWidgetItemsIntoTable_widgetsOnly_maxSpansPerRow5_shouldGroupWidgetsInTable() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
widgetItems, /* maxSpansPerRow= */ 5);
// Row 0: 1x1, 2x2
// Row 1: 2x3, 2x4
// Row 2: 4x4
assertThat(widgetItemInTable).hasSize(3);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
}
@Test
public void groupWidgetItemsIntoTable_widgetsOnly_maxSpansPerRow4_shouldGroupWidgetsInTable() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
widgetItems, /* maxSpansPerRow= */ 4);
// Row 0: 1x1, 2x2
// Row 1: 2x3,
// Row 2: 2x4,
// Row 3: 4x4
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWidgetItemsIntoTable_mixItems_maxSpansPerRow4_shouldGroupWidgetsInTable() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
widgetItems, /* maxSpansPerRow= */ 4);
// Row 0: 1x1, 2x2
// Row 1: 2x3,
// Row 2: 2x4,
// Row 3: 4x4
// Row 4: shortcut3, shortcut1, shortcut2
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
}
private void initTestWidgets() {
List<Point> widgetSizes = List.of(new Point(1, 1), new Point(2, 2), new Point(2, 3),
new Point(2, 4), new Point(4, 4));
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
widgetSizes.stream().forEach(
widgetSize -> {
AppWidgetProviderInfo info = createAppWidgetProviderInfo(
ComponentName.createRelative(
TEST_PACKAGE,
".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y));
LauncherAppWidgetProviderInfo widgetInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
widgetInfo.spanX = widgetSize.x;
widgetInfo.spanY = widgetSize.y;
widgetItems.add(new WidgetItem(widgetInfo, mTestProfile, mIconCache));
}
);
mWidget1x1 = widgetItems.get(0);
mWidget2x2 = widgetItems.get(1);
mWidget2x3 = widgetItems.get(2);
mWidget2x4 = widgetItems.get(3);
mWidget4x4 = widgetItems.get(4);
}
private void initTestShortcuts() {
PackageManager packageManager = mContext.getPackageManager();
mShortcut1 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut1"), UserHandle.CURRENT),
mIconCache, packageManager);
mShortcut2 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut2"), UserHandle.CURRENT),
mIconCache, packageManager);
mShortcut3 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut3"), UserHandle.CURRENT),
mIconCache, packageManager);
}
private final class TestShortcutConfigActivityInfo extends ShortcutConfigActivityInfo {
TestShortcutConfigActivityInfo(ComponentName componentName, UserHandle user) {
super(componentName, user);
}
@Override
public Drawable getFullResIcon(IconCache cache) {
return null;
}
@Override
public CharSequence getLabel(PackageManager pm) {
return null;
}
}
}