1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.allapps.search; 17 18 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH; 19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 20 21 import android.content.Context; 22 import android.os.Handler; 23 24 import androidx.annotation.AnyThread; 25 import androidx.annotation.NonNull; 26 27 import com.android.launcher3.LauncherAppState; 28 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; 29 import com.android.launcher3.model.AllAppsList; 30 import com.android.launcher3.model.BaseModelUpdateTask; 31 import com.android.launcher3.model.BgDataModel; 32 import com.android.launcher3.model.data.AppInfo; 33 import com.android.launcher3.search.SearchAlgorithm; 34 import com.android.launcher3.search.SearchCallback; 35 import com.android.launcher3.search.StringMatcherUtility; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * The default search implementation. 42 */ 43 public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> { 44 45 private static final int MAX_RESULTS_COUNT = 5; 46 47 private final LauncherAppState mAppState; 48 private final Handler mResultHandler; 49 private final boolean mAddNoResultsMessage; 50 DefaultAppSearchAlgorithm(Context context)51 public DefaultAppSearchAlgorithm(Context context) { 52 this(context, false); 53 } 54 DefaultAppSearchAlgorithm(Context context, boolean addNoResultsMessage)55 public DefaultAppSearchAlgorithm(Context context, boolean addNoResultsMessage) { 56 mAppState = LauncherAppState.getInstance(context); 57 mResultHandler = new Handler(MAIN_EXECUTOR.getLooper()); 58 mAddNoResultsMessage = addNoResultsMessage; 59 } 60 61 @Override cancel(boolean interruptActiveRequests)62 public void cancel(boolean interruptActiveRequests) { 63 if (interruptActiveRequests) { 64 mResultHandler.removeCallbacksAndMessages(null); 65 } 66 } 67 68 @Override doSearch(String query, SearchCallback<AdapterItem> callback)69 public void doSearch(String query, SearchCallback<AdapterItem> callback) { 70 mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { 71 @Override 72 public void execute(@NonNull final LauncherAppState app, 73 @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { 74 ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query); 75 if (mAddNoResultsMessage && result.isEmpty()) { 76 result.add(getEmptyMessageAdapterItem(query)); 77 } 78 mResultHandler.post(() -> callback.onSearchResult(query, result)); 79 } 80 }); 81 } 82 getEmptyMessageAdapterItem(String query)83 private static AdapterItem getEmptyMessageAdapterItem(String query) { 84 AdapterItem item = new AdapterItem(VIEW_TYPE_EMPTY_SEARCH); 85 // Add a place holder info to propagate the query 86 AppInfo placeHolder = new AppInfo(); 87 placeHolder.title = query; 88 item.itemInfo = placeHolder; 89 return item; 90 } 91 92 /** 93 * Filters {@link AppInfo}s matching specified query 94 */ 95 @AnyThread getTitleMatchResult(List<AppInfo> apps, String query)96 public static ArrayList<AdapterItem> getTitleMatchResult(List<AppInfo> apps, String query) { 97 // Do an intersection of the words in the query and each title, and filter out all the 98 // apps that don't match all of the words in the query. 99 final String queryTextLower = query.toLowerCase(); 100 final ArrayList<AdapterItem> result = new ArrayList<>(); 101 StringMatcherUtility.StringMatcher matcher = 102 StringMatcherUtility.StringMatcher.getInstance(); 103 104 int resultCount = 0; 105 int total = apps.size(); 106 for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) { 107 AppInfo info = apps.get(i); 108 if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) { 109 result.add(AdapterItem.asApp(info)); 110 resultCount++; 111 } 112 } 113 return result; 114 } 115 } 116