diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml new file mode 100644 index 0000000000..9ab27c1620 --- /dev/null +++ b/res/layout/search_section_title.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 80b511a54c..0ec0581457 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -67,6 +67,10 @@ App + + + Apps + Notifications diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index d65316090c..dec92df2d3 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -66,6 +66,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter mSearchResults; + private ArrayList mSearchResults; private AllAppsGridAdapter mAdapter; private AppInfoComparator mAppNameComparator; private final int mNumAppsPerRow; @@ -210,10 +225,10 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } /** - * Sets the sorted list of filtered components. + * Sets results list for search */ - public boolean setOrderedFilter(ArrayList f) { - if (mSearchResults != f) { + public boolean setSearchResults(ArrayList f) { + if (f == null || mSearchResults != f) { boolean same = mSearchResults != null && mSearchResults.equals(f); mSearchResults = f; onAppsUpdated(); @@ -298,35 +313,42 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections - for (AppInfo info : getFiltersAppInfos()) { - String sectionName = info.sectionName; + if (!hasFilter()) { + for (AppInfo info : mApps) { + String sectionName = info.sectionName; - // Create a new section if the section names do not match - if (!sectionName.equals(lastSectionName)) { - lastSectionName = sectionName; - lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); - mFastScrollerSections.add(lastFastScrollerSectionInfo); - } + // Create a new section if the section names do not match + if (!sectionName.equals(lastSectionName)) { + lastSectionName = sectionName; + lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); + mFastScrollerSections.add(lastFastScrollerSectionInfo); + } + + // Create an app item + AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); + if (lastFastScrollerSectionInfo.fastScrollToItem == null) { + lastFastScrollerSectionInfo.fastScrollToItem = appItem; + } + mAdapterItems.add(appItem); + mFilteredApps.add(info); + } + } else { + mAdapterItems.addAll(mSearchResults); + List appInfos = mSearchResults.stream().filter( + i -> AllAppsGridAdapter.isIconViewType(i.viewType)).map(i -> i.appInfo).collect( + Collectors.toList()); + mFilteredApps.addAll(appInfos); + if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + // Append the search market item + if (hasNoFilteredResults()) { + mAdapterItems.add(AdapterItem.asEmptySearch(position++)); + } else { + mAdapterItems.add(AdapterItem.asAllAppsDivider(position++)); + } + mAdapterItems.add(AdapterItem.asMarketSearch(position++)); - // Create an app item - AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); - if (lastFastScrollerSectionInfo.fastScrollToItem == null) { - lastFastScrollerSectionInfo.fastScrollToItem = appItem; } - mAdapterItems.add(appItem); - mFilteredApps.add(info); } - - if (hasFilter()) { - // Append the search market item - if (hasNoFilteredResults()) { - mAdapterItems.add(AdapterItem.asEmptySearch(position++)); - } else { - mAdapterItems.add(AdapterItem.asAllAppsDivider(position++)); - } - mAdapterItems.add(AdapterItem.asMarketSearch(position++)); - } - if (mNumAppsPerRow != 0) { // Update the number of rows in the adapter after we do all the merging (otherwise, we // would have to shift the values again) @@ -381,18 +403,4 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } } } - - private List getFiltersAppInfos() { - if (mSearchResults == null) { - return mApps; - } - ArrayList result = new ArrayList<>(); - for (ComponentKey key : mSearchResults) { - AppInfo match = mAllAppsStore.getApp(key); - if (match != null) { - result.add(match); - } - } - return result; - } } diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index df1cd26da4..db94e8b6e8 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -28,7 +28,7 @@ import android.widget.TextView.OnEditorActionListener; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Utilities; -import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.util.PackageManagerHelper; import java.util.ArrayList; @@ -50,6 +50,7 @@ public class AllAppsSearchBarController public void setVisibility(int visibility) { mInput.setVisibility(visibility); } + /** * Sets the references to the apps model and the search result callback. */ @@ -164,9 +165,9 @@ public class AllAppsSearchBarController /** * Called when the search is complete. * - * @param apps sorted list of matching components or null if in case of failure. + * @param items sorted list of search result adapter items. */ - void onSearchResult(String query, ArrayList apps); + void onSearchResult(String query, ArrayList items); /** * Called when the search results should be cleared. diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 356c52ca39..df6a89bcb1 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -38,13 +38,13 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Insettable; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.allapps.SearchUiManager; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; @@ -135,7 +135,8 @@ public class AppsSearchContainerLayout extends ExtendedEditText mApps = appsView.getApps(); mAppsView = appsView; mSearchBarController.initialize( - new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this); + new DefaultAppSearchAlgorithm(LauncherAppState.getInstance(mLauncher)), this, + mLauncher, this); } @Override @@ -168,9 +169,9 @@ public class AppsSearchContainerLayout extends ExtendedEditText } @Override - public void onSearchResult(String query, ArrayList apps) { - if (apps != null) { - mApps.setOrderedFilter(apps); + public void onSearchResult(String query, ArrayList items) { + if (items != null) { + mApps.setSearchResults(items); notifyResultChanged(); mAppsView.setLastSearchQuery(query); } @@ -178,7 +179,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText @Override public void clearSearchResult() { - if (mApps.setOrderedFilter(null)) { + if (mApps.setSearchResults(null)) { notifyResultChanged(); } diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java new file mode 100644 index 0000000000..5beb956079 --- /dev/null +++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 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.allapps.search; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BaseModelUpdateTask; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.data.AppInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A device search section for handling app searches + */ +public class AppsSearchPipeline implements SearchPipeline { + + private static final int MAX_RESULTS_COUNT = 5; + + private final SearchSectionInfo mSearchSectionInfo; + private final LauncherAppState mLauncherAppState; + + public AppsSearchPipeline(LauncherAppState launcherAppState) { + mLauncherAppState = launcherAppState; + mSearchSectionInfo = new SearchSectionInfo(R.string.search_corpus_apps); + } + + @Override + @WorkerThread + public void performSearch(String query, Consumer> callback) { + mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + callback.accept(getAdapterItems(getTitleMatchResult(apps.data, query))); + } + }); + } + + /** + * Filters {@link AppInfo}s matching specified query + */ + public static ArrayList getTitleMatchResult(List apps, String query) { + // Do an intersection of the words in the query and each title, and filter out all the + // apps that don't match all of the words in the query. + final String queryTextLower = query.toLowerCase(); + final ArrayList result = new ArrayList<>(); + DefaultAppSearchAlgorithm.StringMatcher matcher = + DefaultAppSearchAlgorithm.StringMatcher.getInstance(); + for (AppInfo info : apps) { + if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) { + result.add(info); + } + } + return result; + } + + private ArrayList getAdapterItems(List matchingApps) { + ArrayList items = new ArrayList<>(); + if (matchingApps.isEmpty()) { + return items; + } + items.add(AdapterItem.asSearchTitle(mSearchSectionInfo, 0)); + int existingItems = items.size(); + int searchResultsCount = Math.min(matchingApps.size(), MAX_RESULTS_COUNT); + for (int i = 0; i < searchResultsCount; i++) { + AdapterItem appItem = AdapterItem.asApp(i + existingItems, "", matchingApps.get(i), i); + appItem.searchSectionInfo = mSearchSectionInfo; + items.add(appItem); + } + + return items; + } +} diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java index f72a9888cf..db10311675 100644 --- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java +++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java @@ -17,24 +17,22 @@ package com.android.launcher3.allapps.search; import android.os.Handler; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.util.ComponentKey; import java.text.Collator; -import java.util.ArrayList; -import java.util.List; /** * The default search implementation. */ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { - private final List mApps; protected final Handler mResultHandler; + private final AppsSearchPipeline mAppsSearchPipeline; - public DefaultAppSearchAlgorithm(List apps) { - mApps = apps; + public DefaultAppSearchAlgorithm(LauncherAppState launcherAppState) { mResultHandler = new Handler(); + mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState); } @Override @@ -47,28 +45,8 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { @Override public void doSearch(final String query, final AllAppsSearchBarController.Callbacks callback) { - final ArrayList result = getTitleMatchResult(query); - mResultHandler.post(new Runnable() { - - @Override - public void run() { - callback.onSearchResult(query, result); - } - }); - } - - private ArrayList getTitleMatchResult(String query) { - // Do an intersection of the words in the query and each title, and filter out all the - // apps that don't match all of the words in the query. - final String queryTextLower = query.toLowerCase(); - final ArrayList result = new ArrayList<>(); - StringMatcher matcher = StringMatcher.getInstance(); - for (AppInfo info : mApps) { - if (matches(info, queryTextLower, matcher)) { - result.add(info.toComponentKey()); - } - } - return result; + mAppsSearchPipeline.performSearch(query, + results -> mResultHandler.post(() -> callback.onSearchResult(query, results))); } public static boolean matches(AppInfo info, String query, StringMatcher matcher) { diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java new file mode 100644 index 0000000000..321674025a --- /dev/null +++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.allapps.search; + +import com.android.launcher3.allapps.AlphabeticalAppsList; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * An interface for handling search within pipeline + */ +public interface SearchPipeline { + + /** + * Perform query + */ + void performSearch(String query, Consumer> cb); +} diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java new file mode 100644 index 0000000000..880b2466dc --- /dev/null +++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 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.allapps.search; + +import android.content.Context; + +/** + * Info class for a search section + */ +public class SearchSectionInfo { + private final int mTitleResId; + + public SearchSectionInfo(int titleResId) { + mTitleResId = titleResId; + } + + /** + * Returns the section's title + */ + public String getTitle(Context context) { + return context.getString(mTitleResId); + } +}