15683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal/*
25683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * Copyright (C) 2015 The Android Open Source Project
35683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal *
45683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * Licensed under the Apache License, Version 2.0 (the "License");
55683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * you may not use this file except in compliance with the License.
65683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * You may obtain a copy of the License at
75683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal *
85683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal *      http://www.apache.org/licenses/LICENSE-2.0
95683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal *
105683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * Unless required by applicable law or agreed to in writing, software
115683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * distributed under the License is distributed on an "AS IS" BASIS,
125683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * See the License for the specific language governing permissions and
145683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal * limitations under the License.
155683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal */
165683f871722254e4e357cf3fb77cd28156278e51Sunny Goyalpackage com.android.launcher3.allapps;
175683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
185683f871722254e4e357cf3fb77cd28156278e51Sunny Goyalimport android.os.Handler;
195183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal
205683f871722254e4e357cf3fb77cd28156278e51Sunny Goyalimport com.android.launcher3.AppInfo;
215183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyalimport com.android.launcher3.util.ComponentKey;
225683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
235683f871722254e4e357cf3fb77cd28156278e51Sunny Goyalimport java.util.ArrayList;
245683f871722254e4e357cf3fb77cd28156278e51Sunny Goyalimport java.util.List;
255683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
265683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal/**
27ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung * The default search implementation.
285683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal */
29ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chungpublic class DefaultAppSearchAlgorithm {
305683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
315683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    private final List<AppInfo> mApps;
32d730f9d74f87b90616e0f0a9c7b6a4c78976f41aSunny Goyal    protected final Handler mResultHandler;
335683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
34ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung    public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
355683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        mApps = apps;
365683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        mResultHandler = new Handler();
375683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    }
385683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
395683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    public void cancel(boolean interruptActiveRequests) {
405683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        if (interruptActiveRequests) {
415683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal            mResultHandler.removeCallbacksAndMessages(null);
425683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        }
435683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    }
445683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
45ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung    public void doSearch(final String query,
46ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung            final AllAppsSearchBarController.Callbacks callback) {
475183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        final ArrayList<ComponentKey> result = getTitleMatchResult(query);
485683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        mResultHandler.post(new Runnable() {
495683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
505683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal            @Override
515683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal            public void run() {
52ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung                callback.onSearchResult(query, result);
535683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal            }
545683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        });
555683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    }
565683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal
575183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal    protected ArrayList<ComponentKey> getTitleMatchResult(String query) {
585183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        // Do an intersection of the words in the query and each title, and filter out all the
595183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        // apps that don't match all of the words in the query.
605183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        final String queryTextLower = query.toLowerCase();
615183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        final ArrayList<ComponentKey> result = new ArrayList<>();
625183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        for (AppInfo info : mApps) {
63e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            if (matches(info, queryTextLower)) {
645183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal                result.add(info.toComponentKey());
655183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal            }
665183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        }
675183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal        return result;
685183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal    }
695183285847816cee9d0db6a8a7ab1a5929163e4eSunny Goyal
70e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal    protected boolean matches(AppInfo info, String query) {
71e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int queryLength = query.length();
72e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal
735683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        String title = info.title.toString();
74e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int titleLength = title.length();
75e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal
76e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        if (titleLength < queryLength || queryLength <= 0) {
77e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            return false;
78e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        }
79e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal
80e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int lastType;
81e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int thisType = Character.UNASSIGNED;
82e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int nextType = Character.getType(title.codePointAt(0));
83e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal
84e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        int end = titleLength - queryLength;
85e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        for (int i = 0; i <= end; i++) {
86e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            lastType = thisType;
87e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            thisType = nextType;
88e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            nextType = i < (titleLength - 1) ?
89e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                    Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
90e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            if (isBreak(thisType, lastType, nextType) &&
91e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                    title.substring(i, i + queryLength).equalsIgnoreCase(query)) {
92e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                return true;
935683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal            }
94e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        }
95e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        return false;
96e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal    }
97e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal
98e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal    /**
99e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     * Returns true if the current point should be a break point. Following cases
100e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     * are considered as break points:
101e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     *      1) Any non space character after a space character
102e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     *      2) Any digit after a non-digit character
103e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     *      3) Any capital character after a digit or small character
104e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     *      4) Any capital character before a small character
105e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal     */
106e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal    protected boolean isBreak(int thisType, int prevType, int nextType) {
107e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal        switch (thisType) {
108e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.UPPERCASE_LETTER:
109e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                if (nextType == Character.UPPERCASE_LETTER) {
110e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                    return true;
111e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                }
112e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                // Follow through
113e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.TITLECASE_LETTER:
114e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                // Break point if previous was not a upper case
115e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                return prevType != Character.UPPERCASE_LETTER;
116e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.LOWERCASE_LETTER:
117e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                // Break point if previous was not a letter.
11828a64381b67d72fcc8b994343507ed9c5821df53Sunny Goyal                return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
119e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.DECIMAL_DIGIT_NUMBER:
120e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.LETTER_NUMBER:
121e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.OTHER_NUMBER:
122e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                // Break point if previous was not a number
123e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                return !(prevType == Character.DECIMAL_DIGIT_NUMBER
124e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                        || prevType == Character.LETTER_NUMBER
125e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                        || prevType == Character.OTHER_NUMBER);
126e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.MATH_SYMBOL:
127e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.CURRENCY_SYMBOL:
128e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.OTHER_PUNCTUATION:
129e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            case Character.DASH_PUNCTUATION:
130e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                // Always a break point for a symbol
131e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal                return true;
132e4a3e0cfaf5b72a54e99bcebdc03eda8aef53091Sunny Goyal            default:
1335683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal                return false;
1345683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal        }
1355683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal    }
1365683f871722254e4e357cf3fb77cd28156278e51Sunny Goyal}
137