Simplifying widget search pipeline
Bug: 183607616 Test: Verified on device Change-Id: I3e5dd9e280f375475d1e1cf41dff6e6533175ebf
This commit is contained in:
parent
07fb2fea34
commit
ce6cc7e705
|
@ -18,64 +18,197 @@ package com.android.launcher3.widget.picker.search;
|
|||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
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 static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.UserHandle;
|
||||
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RunWith(RobolectricTestRunner.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 WidgetsPickerSearchPipeline mSearchPipeline;
|
||||
private PopupDataProvider mDataProvider;
|
||||
@Mock
|
||||
private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
|
||||
@Captor
|
||||
private ArgumentCaptor<Consumer<List<WidgetsListBaseEntry>>> mConsumerCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mSearchPipeline);
|
||||
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 = RuntimeEnvironment.application;
|
||||
|
||||
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 = new SimpleWidgetsSearchAlgorithm(mDataProvider);
|
||||
doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doSearch_shouldQueryPipeline() {
|
||||
mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
|
||||
public void filter_shouldMatchOnAppName() {
|
||||
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
|
||||
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
|
||||
.when(mDataProvider)
|
||||
.getAllWidgets();
|
||||
|
||||
verify(mSearchPipeline).query(eq("abc"), any());
|
||||
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 doSearch_shouldInformSearchCallbackOnQueryResult() {
|
||||
ArrayList<WidgetsListBaseEntry> baseEntries = new ArrayList<>();
|
||||
public void filter_shouldMatchOnWidgetLabel() {
|
||||
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
|
||||
mCameraContentEntry))
|
||||
.when(mDataProvider)
|
||||
.getAllWidgets();
|
||||
|
||||
mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
|
||||
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"));
|
||||
}
|
||||
|
||||
verify(mSearchPipeline).query(eq("abc"), mConsumerCaptor.capture());
|
||||
mConsumerCaptor.getValue().accept(baseEntries);
|
||||
@Test
|
||||
public void doSearch_shouldInformCallback() {
|
||||
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
|
||||
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
|
||||
.when(mDataProvider)
|
||||
.getAllWidgets();
|
||||
mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
// Verify SearchCallback#onSearchResult receives a query token along with the search
|
||||
// results. The query token is the original query string concatenated with the query
|
||||
// timestamp.
|
||||
verify(mSearchCallback).onSearchResult(matches("abc\t\\d*"), eq(baseEntries));
|
||||
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) {
|
||||
ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
|
||||
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
|
||||
for (int i = 0; i < numOfWidgets; i++) {
|
||||
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
|
||||
AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
|
||||
widgetInfo.provider = cn;
|
||||
ReflectionHelpers.setField(widgetInfo, "providerInfo",
|
||||
packageManager.addReceiverIfNotPresent(cn));
|
||||
|
||||
WidgetItem widgetItem = new WidgetItem(
|
||||
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
|
||||
mTestProfile, mIconCache);
|
||||
widgetItems.add(widgetItem);
|
||||
}
|
||||
return widgetItems;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,185 +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 android.os.Looper.getMainLooper;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.UserHandle;
|
||||
|
||||
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.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 org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class SimpleWidgetsSearchPipelineTest {
|
||||
@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;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
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 = RuntimeEnvironment.application;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void query_shouldMatchOnAppName() {
|
||||
SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
|
||||
List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
|
||||
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry));
|
||||
|
||||
pipeline.query("Ca", results ->
|
||||
assertEquals(results,
|
||||
List.of(
|
||||
new WidgetsListSearchHeaderEntry(
|
||||
mCalendarHeaderEntry.mPkgItem,
|
||||
mCalendarHeaderEntry.mTitleSectionName,
|
||||
mCalendarHeaderEntry.mWidgets),
|
||||
mCalendarContentEntry,
|
||||
new WidgetsListSearchHeaderEntry(
|
||||
mCameraHeaderEntry.mPkgItem,
|
||||
mCameraHeaderEntry.mTitleSectionName,
|
||||
mCameraHeaderEntry.mWidgets),
|
||||
mCameraContentEntry)));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void query_shouldMatchOnWidgetLabel() {
|
||||
SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
|
||||
List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
|
||||
mCameraContentEntry));
|
||||
|
||||
pipeline.query("Widget1", results ->
|
||||
assertEquals(results,
|
||||
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)))));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
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) {
|
||||
ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
|
||||
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
|
||||
for (int i = 0; i < numOfWidgets; i++) {
|
||||
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
|
||||
AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
|
||||
widgetInfo.provider = cn;
|
||||
ReflectionHelpers.setField(widgetInfo, "providerInfo",
|
||||
packageManager.addReceiverIfNotPresent(cn));
|
||||
|
||||
WidgetItem widgetItem = new WidgetItem(
|
||||
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
|
||||
mTestProfile, mIconCache);
|
||||
widgetItems.add(widgetItem);
|
||||
}
|
||||
return widgetItems;
|
||||
}
|
||||
}
|
|
@ -169,7 +169,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
|||
onWidgetsBound();
|
||||
|
||||
mSearchAndRecommendationViewHolder.mSearchBar.initialize(
|
||||
mLauncher.getPopupDataProvider().getAllWidgets(), /* searchModeListener= */ this);
|
||||
mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,10 +26,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.search.SearchAlgorithm;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
|
||||
import java.util.List;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
|
||||
/**
|
||||
* View for a search bar with an edit text with a cancel button.
|
||||
|
@ -54,12 +51,10 @@ public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSea
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initialize(List<WidgetsListBaseEntry> allWidgets,
|
||||
SearchModeListener searchModeListener) {
|
||||
SearchAlgorithm<WidgetsListBaseEntry> algo =
|
||||
new SimpleWidgetsSearchAlgorithm(new SimpleWidgetsSearchPipeline(allWidgets));
|
||||
public void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener) {
|
||||
mController = new WidgetsSearchBarController(
|
||||
algo, mEditText, mCancelButton, searchModeListener);
|
||||
new SimpleWidgetsSearchAlgorithm(dataProvider),
|
||||
mEditText, mCancelButton, searchModeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,42 +16,41 @@
|
|||
|
||||
package com.android.launcher3.widget.picker.search;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import static com.android.launcher3.search.StringMatcherUtility.matches;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.search.SearchAlgorithm;
|
||||
import com.android.launcher3.search.SearchCallback;
|
||||
import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of {@link SearchAlgorithm} that posts a task to query on the main thread.
|
||||
*/
|
||||
public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "SimpleWidgetsSearchAlgo";
|
||||
private static final String DELIM = "\t";
|
||||
|
||||
private final Handler mResultHandler;
|
||||
private final WidgetsPickerSearchPipeline mSearchPipeline;
|
||||
private final PopupDataProvider mDataProvider;
|
||||
|
||||
public SimpleWidgetsSearchAlgorithm(WidgetsPickerSearchPipeline searchPipeline) {
|
||||
public SimpleWidgetsSearchAlgorithm(PopupDataProvider dataProvider) {
|
||||
mResultHandler = new Handler();
|
||||
mSearchPipeline = searchPipeline;
|
||||
mDataProvider = dataProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSearch(String query, SearchCallback<WidgetsListBaseEntry> callback) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String queryToken = query + DELIM + startTime;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "doSearch queryToken:" + queryToken);
|
||||
}
|
||||
mSearchPipeline.query(query,
|
||||
results -> mResultHandler.post(
|
||||
() -> callback.onSearchResult(queryToken, new ArrayList(results))));
|
||||
ArrayList<WidgetsListBaseEntry> result = getFilteredWidgets(mDataProvider, query);
|
||||
mResultHandler.post(() -> callback.onSearchResult(query, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,4 +59,36 @@ public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<Widge
|
|||
mResultHandler.removeCallbacksAndMessages(/*token= */null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entries for all matched widgets
|
||||
*/
|
||||
public static ArrayList<WidgetsListBaseEntry> getFilteredWidgets(
|
||||
PopupDataProvider dataProvider, String input) {
|
||||
ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
|
||||
dataProvider.getAllWidgets().stream()
|
||||
.filter(entry -> entry instanceof WidgetsListHeaderEntry)
|
||||
.forEach(headerEntry -> {
|
||||
List<WidgetItem> matchedWidgetItems = filterWidgetItems(
|
||||
input, headerEntry.mPkgItem.title.toString(), headerEntry.mWidgets);
|
||||
if (matchedWidgetItems.size() > 0) {
|
||||
results.add(new WidgetsListSearchHeaderEntry(headerEntry.mPkgItem,
|
||||
headerEntry.mTitleSectionName, matchedWidgetItems));
|
||||
results.add(new WidgetsListContentEntry(headerEntry.mPkgItem,
|
||||
headerEntry.mTitleSectionName, matchedWidgetItems));
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
private static List<WidgetItem> filterWidgetItems(String query, String packageTitle,
|
||||
List<WidgetItem> items) {
|
||||
StringMatcher matcher = StringMatcher.getInstance();
|
||||
if (matches(query, packageTitle, matcher)) {
|
||||
return items;
|
||||
}
|
||||
return items.stream()
|
||||
.filter(item -> matches(query, item.label, matcher))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,72 +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 com.android.launcher3.search.StringMatcherUtility.matches;
|
||||
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of {@link WidgetsPickerSearchPipeline} that performs search by prefix matching on
|
||||
* app names and widget labels.
|
||||
*/
|
||||
public final class SimpleWidgetsSearchPipeline implements WidgetsPickerSearchPipeline {
|
||||
|
||||
private final List<WidgetsListBaseEntry> mAllEntries;
|
||||
|
||||
public SimpleWidgetsSearchPipeline(List<WidgetsListBaseEntry> allEntries) {
|
||||
mAllEntries = allEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void query(String input, Consumer<List<WidgetsListBaseEntry>> callback) {
|
||||
ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
|
||||
mAllEntries.stream().filter(entry -> entry instanceof WidgetsListHeaderEntry)
|
||||
.forEach(headerEntry -> {
|
||||
List<WidgetItem> matchedWidgetItems = filterWidgetItems(
|
||||
input, headerEntry.mPkgItem.title.toString(), headerEntry.mWidgets);
|
||||
if (matchedWidgetItems.size() > 0) {
|
||||
results.add(new WidgetsListSearchHeaderEntry(headerEntry.mPkgItem,
|
||||
headerEntry.mTitleSectionName, matchedWidgetItems));
|
||||
results.add(new WidgetsListContentEntry(headerEntry.mPkgItem,
|
||||
headerEntry.mTitleSectionName, matchedWidgetItems));
|
||||
}
|
||||
});
|
||||
callback.accept(results);
|
||||
}
|
||||
|
||||
private List<WidgetItem> filterWidgetItems(String query, String packageTitle,
|
||||
List<WidgetItem> items) {
|
||||
StringMatcher matcher = StringMatcher.getInstance();
|
||||
if (matches(query, packageTitle, matcher)) {
|
||||
return items;
|
||||
}
|
||||
return items.stream()
|
||||
.filter(item -> matches(query, item.label, matcher))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -1,44 +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 com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An interface for a pipeline to handle widgets search.
|
||||
*/
|
||||
public interface WidgetsPickerSearchPipeline {
|
||||
|
||||
/**
|
||||
* Performs a search query asynchronically. Invokes {@code callback} when the search is
|
||||
* complete.
|
||||
*/
|
||||
void query(String input, Consumer<List<WidgetsListBaseEntry>> callback);
|
||||
|
||||
/**
|
||||
* Cancels any ongoing search request.
|
||||
*/
|
||||
default void cancel() {};
|
||||
|
||||
/**
|
||||
* Cleans up after search is no longer needed.
|
||||
*/
|
||||
default void destroy() {};
|
||||
}
|
|
@ -16,9 +16,7 @@
|
|||
|
||||
package com.android.launcher3.widget.picker.search;
|
||||
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
|
||||
import java.util.List;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
|
||||
/**
|
||||
* Interface for a widgets picker search bar.
|
||||
|
@ -27,7 +25,7 @@ public interface WidgetsSearchBar {
|
|||
/**
|
||||
* Attaches a controller to the search bar which interacts with {@code searchModeListener}.
|
||||
*/
|
||||
void initialize(List<WidgetsListBaseEntry> allWidgets, SearchModeListener searchModeListener);
|
||||
void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener);
|
||||
|
||||
/**
|
||||
* Clears search bar.
|
||||
|
|
Loading…
Reference in New Issue