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;
17 
18 import android.os.Handler;
19 
20 import com.android.launcher3.AppInfo;
21 import com.android.launcher3.util.ComponentKey;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 
26 /**
27  * The default search implementation.
28  */
29 public class DefaultAppSearchAlgorithm {
30 
31     private final List<AppInfo> mApps;
32     protected final Handler mResultHandler;
33 
DefaultAppSearchAlgorithm(List<AppInfo> apps)34     public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
35         mApps = apps;
36         mResultHandler = new Handler();
37     }
38 
cancel(boolean interruptActiveRequests)39     public void cancel(boolean interruptActiveRequests) {
40         if (interruptActiveRequests) {
41             mResultHandler.removeCallbacksAndMessages(null);
42         }
43     }
44 
doSearch(final String query, final AllAppsSearchBarController.Callbacks callback)45     public void doSearch(final String query,
46             final AllAppsSearchBarController.Callbacks callback) {
47         final ArrayList<ComponentKey> result = getTitleMatchResult(query);
48         mResultHandler.post(new Runnable() {
49 
50             @Override
51             public void run() {
52                 callback.onSearchResult(query, result);
53             }
54         });
55     }
56 
getTitleMatchResult(String query)57     protected ArrayList<ComponentKey> getTitleMatchResult(String query) {
58         // Do an intersection of the words in the query and each title, and filter out all the
59         // apps that don't match all of the words in the query.
60         final String queryTextLower = query.toLowerCase();
61         final ArrayList<ComponentKey> result = new ArrayList<>();
62         for (AppInfo info : mApps) {
63             if (matches(info, queryTextLower)) {
64                 result.add(info.toComponentKey());
65             }
66         }
67         return result;
68     }
69 
matches(AppInfo info, String query)70     protected boolean matches(AppInfo info, String query) {
71         int queryLength = query.length();
72 
73         String title = info.title.toString();
74         int titleLength = title.length();
75 
76         if (titleLength < queryLength || queryLength <= 0) {
77             return false;
78         }
79 
80         int lastType;
81         int thisType = Character.UNASSIGNED;
82         int nextType = Character.getType(title.codePointAt(0));
83 
84         int end = titleLength - queryLength;
85         for (int i = 0; i <= end; i++) {
86             lastType = thisType;
87             thisType = nextType;
88             nextType = i < (titleLength - 1) ?
89                     Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
90             if (isBreak(thisType, lastType, nextType) &&
91                     title.substring(i, i + queryLength).equalsIgnoreCase(query)) {
92                 return true;
93             }
94         }
95         return false;
96     }
97 
98     /**
99      * Returns true if the current point should be a break point. Following cases
100      * are considered as break points:
101      *      1) Any non space character after a space character
102      *      2) Any digit after a non-digit character
103      *      3) Any capital character after a digit or small character
104      *      4) Any capital character before a small character
105      */
106     protected boolean isBreak(int thisType, int prevType, int nextType) {
107         switch (thisType) {
108             case Character.UPPERCASE_LETTER:
109                 if (nextType == Character.UPPERCASE_LETTER) {
110                     return true;
111                 }
112                 // Follow through
113             case Character.TITLECASE_LETTER:
114                 // Break point if previous was not a upper case
115                 return prevType != Character.UPPERCASE_LETTER;
116             case Character.LOWERCASE_LETTER:
117                 // Break point if previous was not a letter.
118                 return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
119             case Character.DECIMAL_DIGIT_NUMBER:
120             case Character.LETTER_NUMBER:
121             case Character.OTHER_NUMBER:
122                 // Break point if previous was not a number
123                 return !(prevType == Character.DECIMAL_DIGIT_NUMBER
124                         || prevType == Character.LETTER_NUMBER
125                         || prevType == Character.OTHER_NUMBER);
126             case Character.MATH_SYMBOL:
127             case Character.CURRENCY_SYMBOL:
128             case Character.OTHER_PUNCTUATION:
129             case Character.DASH_PUNCTUATION:
130                 // Always a break point for a symbol
131                 return true;
132             default:
133                 return false;
134         }
135     }
136 }
137