Merge "Revert "Move widget picker tests to Robolectric"" into main

This commit is contained in:
Uwais Ashraf
2024-05-21 23:05:12 +00:00
committed by Android (Google) Code Review
10 changed files with 18 additions and 20 deletions
@@ -15,12 +15,15 @@
*/
package com.android.launcher3.util;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Process;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -84,10 +87,10 @@ public class WidgetUtils {
* Creates a {@link AppWidgetProviderInfo} for the provided component name
*/
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.providerInfo = activityInfo;
AppWidgetProviderInfo info = AppWidgetManager.getInstance(getApplicationContext())
.getInstalledProvidersForPackage(
getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
.get(0);
info.provider = cn;
return info;
}
@@ -1,99 +0,0 @@
/*
* Copyright (C) 2024 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 android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.widget.WidgetImageView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever
@MediumTest
@RunWith(AndroidJUnit4::class)
class WidgetImageViewTest {
private lateinit var context: Context
private lateinit var widgetImageView: WidgetImageView
@Mock private lateinit var testDrawable: Drawable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
widgetImageView = spy(WidgetImageView(context))
}
@Test
fun getBitmapBounds_aspectRatioLargerThanView_scaledByWidth() {
// view - 100 x 100
whenever(widgetImageView.width).thenReturn(100)
whenever(widgetImageView.height).thenReturn(100)
// bitmap - 200 x 100
whenever(testDrawable.intrinsicWidth).thenReturn(200)
whenever(testDrawable.intrinsicHeight).thenReturn(100)
widgetImageView.drawable = testDrawable
val bitmapBounds = widgetImageView.bitmapBounds
// new scaled width of bitmap is = 100, and height is scaled to 1/2 = 50
assertThat(bitmapBounds).isEqualTo(Rect(0, 25, 100, 75))
}
@Test
fun getBitmapBounds_aspectRatioSmallerThanView_scaledByHeight() {
// view - 100 x 100
whenever(widgetImageView.width).thenReturn(100)
whenever(widgetImageView.height).thenReturn(100)
// bitmap - 100 x 200
whenever(testDrawable.intrinsicWidth).thenReturn(100)
whenever(testDrawable.intrinsicHeight).thenReturn(200)
widgetImageView.drawable = testDrawable
val bitmapBounds = widgetImageView.bitmapBounds
// new scaled height of bitmap is = 100, and width is scaled to 1/2 = 50
assertThat(bitmapBounds).isEqualTo(Rect(25, 0, 75, 100))
}
@Test
fun getBitmapBounds_noScale_returnsOriginalDrawableBounds() {
// view - 200 x 100
whenever(widgetImageView.width).thenReturn(200)
whenever(widgetImageView.height).thenReturn(100)
// bitmap - 200 x 100
whenever(testDrawable.intrinsicWidth).thenReturn(200)
whenever(testDrawable.intrinsicHeight).thenReturn(100)
widgetImageView.drawable = testDrawable
val bitmapBounds = widgetImageView.bitmapBounds
// no scaling
assertThat(bitmapBounds).isEqualTo(Rect(0, 0, 200, 100))
}
}
@@ -1,166 +0,0 @@
/*
* Copyright (C) 2024 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 android.content.pm.ApplicationInfo.CATEGORY_AUDIO;
import static android.content.pm.ApplicationInfo.CATEGORY_IMAGE;
import static android.content.pm.ApplicationInfo.CATEGORY_NEWS;
import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
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.when;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.os.Process;
import androidx.test.core.content.pm.ApplicationInfoBuilder;
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.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WidgetRecommendationCategoryProviderTest {
private static final String TEST_PACKAGE = "com.foo.test";
private static final String TEST_APP_NAME = "foo";
private static final WidgetRecommendationCategory PRODUCTIVITY =
new WidgetRecommendationCategory(
R.string.productivity_widget_recommendation_category_label,
/*order=*/0);
private static final WidgetRecommendationCategory NEWS =
new WidgetRecommendationCategory(
R.string.news_widget_recommendation_category_label, /*order=*/1);
private static final WidgetRecommendationCategory SUGGESTED_FOR_YOU =
new WidgetRecommendationCategory(
R.string.others_widget_recommendation_category_label, /*order=*/2);
private static final WidgetRecommendationCategory SOCIAL =
new WidgetRecommendationCategory(
R.string.social_widget_recommendation_category_label,
/*order=*/3);
private static final WidgetRecommendationCategory ENTERTAINMENT =
new WidgetRecommendationCategory(
R.string.entertainment_widget_recommendation_category_label,
/*order=*/4);
private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
TEST_PACKAGE).setName(TEST_APP_NAME).build();
private Context mContext;
@Mock
private IconCache mIconCache;
private WidgetItem mTestWidgetItem;
@Mock
private LauncherApps mLauncherApps;
private InvariantDeviceProfile mTestProfile;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
@Override
public Object getSystemService(String name) {
return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService(
name);
}
};
mTestAppInfo.flags = FLAG_INSTALLED;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
createTestWidgetItem();
}
@Test
public void getWidgetRecommendationCategory_returnsMappedCategory() throws Exception {
ImmutableMap<Integer, WidgetRecommendationCategory> testCategories = ImmutableMap.of(
CATEGORY_PRODUCTIVITY, PRODUCTIVITY,
CATEGORY_NEWS, NEWS,
CATEGORY_SOCIAL, SOCIAL,
CATEGORY_AUDIO, ENTERTAINMENT,
CATEGORY_IMAGE, ENTERTAINMENT,
CATEGORY_VIDEO, ENTERTAINMENT,
CATEGORY_UNDEFINED, SUGGESTED_FOR_YOU);
for (Map.Entry<Integer, WidgetRecommendationCategory> testCategory :
testCategories.entrySet()) {
mTestAppInfo.category = testCategory.getKey();
when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
/*flags=*/ eq(0),
/*user=*/ eq(Process.myUserHandle())))
.thenReturn(mTestAppInfo);
WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
mContext,
mTestWidgetItem)).get();
assertThat(category).isEqualTo(testCategory.getValue());
}
}
private void createTestWidgetItem() {
String widgetLabel = "Foo Widget";
String widgetClassName = ".mWidget";
doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
.createRelative(TEST_PACKAGE, widgetClassName));
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
launcherAppWidgetProviderInfo.spanX = 2;
launcherAppWidgetProviderInfo.spanY = 2;
launcherAppWidgetProviderInfo.label = widgetLabel;
mTestWidgetItem = new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache,
mContext
);
}
}
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2024 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.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.R;
import com.android.launcher3.util.ActivityContextWrapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WidgetsListHeaderAccessibilityTest {
private Context mContext;
private LayoutInflater mLayoutInflater;
@Mock
private View.OnClickListener mOnClickListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
mLayoutInflater = LayoutInflater.from(
new ContextThemeWrapper(mContext, R.style.WidgetContainerTheme));
}
@Test
public void singlePaneCollapsable_hasCustomAccessibilityActions() {
WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
R.layout.widgets_list_row_header,
new FrameLayout(mContext), false);
assertThat(header.getAccessibilityDelegate()).isNotNull();
header.setOnClickListener(mOnClickListener);
header.getAccessibilityDelegate().performAccessibilityAction(header,
AccessibilityNodeInfo.ACTION_EXPAND, null);
header.getAccessibilityDelegate().performAccessibilityAction(header,
AccessibilityNodeInfo.ACTION_COLLAPSE, null);
verify(mOnClickListener, times(2)).onClick(header);
}
@Test
public void twoPaneNonCollapsable_noCustomAccessibilityDelegate() {
WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
R.layout.widgets_list_row_header_two_pane,
new FrameLayout(mContext), false);
assertThat(header.getAccessibilityDelegate()).isNull();
}
}
@@ -1,156 +0,0 @@
/*
* 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.os.UserHandle;
import android.view.ContextThemeWrapper;
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(new ContextThemeWrapper(getApplicationContext(),
R.style.WidgetContainerTheme));
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,
false);
}
@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(PackageUserKey.fromPackageItemInfo(entry.mPkgItem)));
}
private WidgetsListHeaderEntry generateSampleAppHeader(String appName, String packageName,
int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName, UserHandle.CURRENT);
appInfo.title = appName;
appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
return WidgetsListHeaderEntry.create(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, mContext));
}
return widgetItems;
}
}
@@ -1,161 +0,0 @@
/*
* 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.os.UserHandle;
import android.view.ContextThemeWrapper;
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.WidgetManagerHelper;
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(
mContext,
LayoutInflater.from(new ContextThemeWrapper(mContext,
com.android.launcher3.R.style.WidgetContainerTheme)),
mOnIconClickListener,
mOnLongClickListener);
}
@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, UserHandle.CURRENT);
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) {
WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext);
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, mContext, widgetManager));
}
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);
}
}
@@ -1,262 +0,0 @@
/*
* 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 android.content.Context;
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.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,
UserHandle.CURRENT);
private final PackageItemInfo mPackageItemInfo2 = new PackageItemInfo(PACKAGE_NAME_2,
UserHandle.CURRENT);
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);
Context context = getApplicationContext();
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
launcherAppWidgetProviderInfo.spanX = spanX;
launcherAppWidgetProviderInfo.spanY = spanY;
launcherAppWidgetProviderInfo.label = label;
return new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache, context);
}
}
@@ -1,211 +0,0 @@
/*
* 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
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 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(
WidgetsListHeaderEntry.createForSearch(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets),
mCalendarContentEntry,
WidgetsListHeaderEntry.createForSearch(
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(
WidgetsListHeaderEntry.createForSearch(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets.subList(1, 2)),
new WidgetsListContentEntry(
mCalendarHeaderEntry.mPkgItem,
mCalendarHeaderEntry.mTitleSectionName,
mCalendarHeaderEntry.mWidgets.subList(1, 2)),
WidgetsListHeaderEntry.createForSearch(
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);
getInstrumentation().waitForIdleSync();
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 WidgetsListHeaderEntry.create(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, userHandle);
pInfo.title = appName;
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, mContext);
widgetItems.add(widgetItem);
}
return widgetItems;
}
}
@@ -1,162 +0,0 @@
/*
* Copyright (C) 2024 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 android.content.ComponentName
import android.content.Context
import android.graphics.Point
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.WidgetItem
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class WidgetPreviewContainerSizesTest {
private lateinit var context: Context
private lateinit var deviceProfile: DeviceProfile
private lateinit var testInvariantProfile: InvariantDeviceProfile
private lateinit var widgetItemInvariantProfile: InvariantDeviceProfile
@Mock private lateinit var iconCache: IconCache
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
testInvariantProfile = LauncherAppState.getIDP(context)
widgetItemInvariantProfile =
InvariantDeviceProfile().apply {
numRows = TEST_GRID_SIZE
numColumns = TEST_GRID_SIZE
}
deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
}
@Test
fun widgetPreviewContainerSize_forItem_returnsCorrectContainerSize() {
val testSizes = getTestSizes(deviceProfile)
val expectedPreviewContainers = testSizes.values.toList()
for ((index, widgetSize) in testSizes.keys.withIndex()) {
val widgetItem =
createWidgetItem(widgetSize, context, widgetItemInvariantProfile, iconCache)
assertWithMessage("size for $widgetSize should be: ${expectedPreviewContainers[index]}")
.that(WidgetPreviewContainerSize.forItem(widgetItem, deviceProfile))
.isEqualTo(expectedPreviewContainers[index])
}
}
companion object {
private const val TEST_PACKAGE = "com.google.test"
private const val TEST_GRID_SIZE = 6
private val HANDHELD_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
mapOf(
// 1x1
Point(1, 1) to WidgetPreviewContainerSize(1, 1),
// 2x1
Point(2, 1) to WidgetPreviewContainerSize(2, 1),
Point(3, 1) to WidgetPreviewContainerSize(2, 1),
// 4x1
Point(4, 1) to WidgetPreviewContainerSize(4, 1),
// 2x2
Point(2, 2) to WidgetPreviewContainerSize(2, 2),
Point(3, 3) to WidgetPreviewContainerSize(2, 2),
Point(3, 2) to WidgetPreviewContainerSize(2, 2),
// 2x3
Point(2, 3) to WidgetPreviewContainerSize(2, 3),
Point(3, 4) to WidgetPreviewContainerSize(2, 3),
Point(3, 5) to WidgetPreviewContainerSize(2, 3),
// 4x2
Point(4, 2) to WidgetPreviewContainerSize(4, 2),
// 4x3
Point(4, 3) to WidgetPreviewContainerSize(4, 3),
Point(4, 4) to WidgetPreviewContainerSize(4, 3),
)
private val TABLET_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
mapOf(
// 1x1
Point(1, 1) to WidgetPreviewContainerSize(1, 1),
// 2x1
Point(2, 1) to WidgetPreviewContainerSize(2, 1),
// 3x1
Point(3, 1) to WidgetPreviewContainerSize(3, 1),
Point(4, 1) to WidgetPreviewContainerSize(3, 1),
// 2x2
Point(2, 2) to WidgetPreviewContainerSize(2, 2),
// 2x3
Point(2, 3) to WidgetPreviewContainerSize(2, 3),
// 3x2
Point(3, 2) to WidgetPreviewContainerSize(3, 2),
Point(4, 2) to WidgetPreviewContainerSize(3, 2),
Point(5, 2) to WidgetPreviewContainerSize(3, 2),
// 3x3
Point(3, 3) to WidgetPreviewContainerSize(3, 3),
Point(4, 4) to WidgetPreviewContainerSize(3, 3),
// 3x4
Point(5, 4) to WidgetPreviewContainerSize(3, 4),
Point(3, 4) to WidgetPreviewContainerSize(3, 4),
Point(5, 5) to WidgetPreviewContainerSize(3, 4),
Point(6, 4) to WidgetPreviewContainerSize(3, 4),
Point(6, 5) to WidgetPreviewContainerSize(3, 4),
)
private fun getTestSizes(dp: DeviceProfile) =
if (dp.isTablet && !dp.isTwoPanels) {
TABLET_TEST_SIZES
} else {
HANDHELD_TEST_SIZES
}
private fun createWidgetItem(
widgetSize: Point,
context: Context,
invariantDeviceProfile: InvariantDeviceProfile,
iconCache: IconCache
): WidgetItem {
val providerInfo =
createAppWidgetProviderInfo(
ComponentName.createRelative(
TEST_PACKAGE,
/*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y
)
)
val widgetInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo).apply {
spanX = widgetSize.x
spanY = widgetSize.y
}
return WidgetItem(widgetInfo, invariantDeviceProfile, iconCache, context)
}
}
}
@@ -1,312 +0,0 @@
/*
* 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 static org.mockito.Mockito.when;
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.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
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.util.ActivityContextWrapper;
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.Mockito;
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";
private static final int SPACE_SIZE = 10;
// Note - actual widget size includes SPACE_SIZE (border) + cell padding.
private static final int CELL_SIZE = 50;
private static final int NUM_OF_COLS = 5;
private static final int NUM_OF_ROWS = 5;
@Mock
private IconCache mIconCache;
private DeviceProfile mTestDeviceProfile;
private Context mContext;
private InvariantDeviceProfile mTestInvariantProfile;
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 = new ActivityContextWrapper(getApplicationContext());
mTestInvariantProfile = new InvariantDeviceProfile();
mTestInvariantProfile.numColumns = NUM_OF_COLS;
mTestInvariantProfile.numRows = NUM_OF_ROWS;
initDP();
initTestWidgets();
initTestShortcuts();
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
.getComponent().getPackageName())
.when(mIconCache).getTitleNoCache(any());
}
@Test
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
// With reordering, rows displayed in order of increasing size.
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container - 110px)
// Row 2: 2x3(in a 2x3 container - 110px), 2x4(in a 2x3 container - 110px)
// Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 10);
// With reordering, but space taken up by cell padding, so, no grouping (even if 2x2 and 2x3
// use same preview container).
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 130px)
// Row 2: 2x3(in a 2x3 container: 130px)
// Row 3: 2x4(in a 2x3 container: 130px)
// Row 4: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mWidget4x4);
}
@Test
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow260_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 260, 10);
// With reordering, even with cellPadding, enough space to group 2x3 and 2x4 (which also use
// same container)
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 130px)
// Row 2: 2x3(in a 2x3 container: 130px), 2x4(in a 2x3 container: 130px)
// Row 3: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
// With reordering, rows displayed in order of increasing size.
// Row 0: 1x1(50px)
// Row 1: 2x2(in a 2x2 container: 110px)
// Row 2: 2x3(in a 2x3 container: 110px), 2x4(in a 2x3 container: 110px)
// Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
assertThat(widgetItemInTable).hasSize(4);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
public void groupWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
// With reordering - rows displays in order of increasing size:
// Row 0: 1x1(50px)
// Row 1: 2x2(110px)
// Row 2: 2x3 (in a 2x3 container 110px), 2x4 (in a 2x3 container 110px)
// Row 3: 4x4 (in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
// Row 4: shortcut3, shortcut1, shortcut2 (shortcuts are always displayed at bottom)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
}
@Test
public void groupWithoutReordering_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems =
List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2);
List<ArrayList<WidgetItem>> widgetItemInTable =
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
// Without reordering, widgets are grouped only if the next one fits and uses same preview
// container:
// Row 0: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
// Row 1: 2x3(in a 2x3 container - 110px)
// Row 2: 1x1(50px)
// Row 3: 2x4(in a 2x3 container - 110px)
// Row 4: 2x2(in a 2x2 container - 110px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
assertThat(widgetItemInTable.get(2)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
assertThat(widgetItemInTable.get(4)).containsExactly(mWidget2x2);
}
private void initDP() {
DeviceProfile dp = LauncherAppState.getIDP(mContext)
.getDeviceProfile(mContext).copy(mContext);
mTestDeviceProfile = Mockito.spy(dp);
doAnswer(i -> {
((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
return null;
}).when(mTestDeviceProfile).getCellSize(any(Point.class));
when(mTestDeviceProfile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
mTestDeviceProfile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
mTestDeviceProfile.widgetPadding.setEmpty();
mTestDeviceProfile.allAppsIconSizePx = 0;
}
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, mTestInvariantProfile, mIconCache, mContext));
});
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;
}
}
}