Loading src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java +53 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.os.Handler; import com.android.launcher3.AppInfo; import com.android.launcher3.util.ComponentKey; import java.text.Collator; import java.util.ArrayList; import java.util.List; Loading Loading @@ -61,8 +62,9 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { // apps that don't match all of the words in the query. final String queryTextLower = query.toLowerCase(); final ArrayList<ComponentKey> result = new ArrayList<>(); StringMatcher matcher = StringMatcher.getInstance(); for (AppInfo info : mApps) { if (matches(info, queryTextLower)) { if (matches(info, queryTextLower, matcher)) { result.add(info.toComponentKey()); } } Loading @@ -70,6 +72,10 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { } public static boolean matches(AppInfo info, String query) { return matches(info, query, StringMatcher.getInstance()); } public static boolean matches(AppInfo info, String query, StringMatcher matcher) { int queryLength = query.length(); String title = info.title.toString(); Loading @@ -90,7 +96,7 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { nextType = i < (titleLength - 1) ? Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED; if (isBreak(thisType, lastType, nextType) && title.substring(i, i + queryLength).equalsIgnoreCase(query)) { matcher.matches(query, title.substring(i, i + queryLength))) { return true; } } Loading @@ -106,6 +112,13 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { * 4) Any capital character before a small character */ private static boolean isBreak(int thisType, int prevType, int nextType) { switch (prevType) { case Character.UNASSIGNED: case Character.SPACE_SEPARATOR: case Character.LINE_SEPARATOR: case Character.PARAGRAPH_SEPARATOR: return true; } switch (thisType) { case Character.UPPERCASE_LETTER: if (nextType == Character.UPPERCASE_LETTER) { Loading @@ -132,8 +145,44 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { // Always a break point for a symbol return true; default: // Always a break point at first character return prevType == Character.UNASSIGNED; return false; } } public static class StringMatcher { private static final char MAX_UNICODE = '\uFFFF'; private final Collator mCollator; StringMatcher() { // On android N and above, Collator uses ICU implementation which has a much better // support for non-latin locales. mCollator = Collator.getInstance(); mCollator.setStrength(Collator.PRIMARY); mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); } /** * Returns true if {@param query} is a prefix of {@param target} */ public boolean matches(String query, String target) { switch (mCollator.compare(query, target)) { case 0: return true; case -1: // The target string can contain a modifier which would make it larger than // the query string (even though the length is same). If the query becomes // larger after appending a unicode character, it was originally a prefix of // the target string and hence should match. return mCollator.compare(query + MAX_UNICODE, target) > -1; default: return false; } } public static StringMatcher getInstance() { return new StringMatcher(); } } } tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java +20 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.ComponentName; import android.test.InstrumentationTestCase; import com.android.launcher3.AppInfo; import com.android.launcher3.Utilities; import java.util.ArrayList; import java.util.List; Loading Loading @@ -75,6 +76,25 @@ public class DefaultAppSearchAlgorithmTest extends InstrumentationTestCase { assertTrue(mAlgorithm.matches(getInfo("电子邮件"), "电子")); assertFalse(mAlgorithm.matches(getInfo("电子邮件"), "子")); assertFalse(mAlgorithm.matches(getInfo("电子邮件"), "邮件")); assertFalse(mAlgorithm.matches(getInfo("Bot"), "ba")); assertFalse(mAlgorithm.matches(getInfo("bot"), "ba")); } public void testMatchesVN() { if (!Utilities.ATLEAST_NOUGAT) { return; } assertTrue(mAlgorithm.matches(getInfo("다운로드"), "다")); assertTrue(mAlgorithm.matches(getInfo("드라이브"), "드")); assertTrue(mAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ")); assertTrue(mAlgorithm.matches(getInfo("운로 드라이브"), "ㄷ")); assertTrue(mAlgorithm.matches(getInfo("abc"), "åbç")); assertTrue(mAlgorithm.matches(getInfo("Alpha"), "ål")); assertFalse(mAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷㄷ")); assertFalse(mAlgorithm.matches(getInfo("로드라이브"), "ㄷ")); assertFalse(mAlgorithm.matches(getInfo("abc"), "åç")); } private AppInfo getInfo(String title) { Loading Loading
src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java +53 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.os.Handler; import com.android.launcher3.AppInfo; import com.android.launcher3.util.ComponentKey; import java.text.Collator; import java.util.ArrayList; import java.util.List; Loading Loading @@ -61,8 +62,9 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { // apps that don't match all of the words in the query. final String queryTextLower = query.toLowerCase(); final ArrayList<ComponentKey> result = new ArrayList<>(); StringMatcher matcher = StringMatcher.getInstance(); for (AppInfo info : mApps) { if (matches(info, queryTextLower)) { if (matches(info, queryTextLower, matcher)) { result.add(info.toComponentKey()); } } Loading @@ -70,6 +72,10 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { } public static boolean matches(AppInfo info, String query) { return matches(info, query, StringMatcher.getInstance()); } public static boolean matches(AppInfo info, String query, StringMatcher matcher) { int queryLength = query.length(); String title = info.title.toString(); Loading @@ -90,7 +96,7 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { nextType = i < (titleLength - 1) ? Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED; if (isBreak(thisType, lastType, nextType) && title.substring(i, i + queryLength).equalsIgnoreCase(query)) { matcher.matches(query, title.substring(i, i + queryLength))) { return true; } } Loading @@ -106,6 +112,13 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { * 4) Any capital character before a small character */ private static boolean isBreak(int thisType, int prevType, int nextType) { switch (prevType) { case Character.UNASSIGNED: case Character.SPACE_SEPARATOR: case Character.LINE_SEPARATOR: case Character.PARAGRAPH_SEPARATOR: return true; } switch (thisType) { case Character.UPPERCASE_LETTER: if (nextType == Character.UPPERCASE_LETTER) { Loading @@ -132,8 +145,44 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { // Always a break point for a symbol return true; default: // Always a break point at first character return prevType == Character.UNASSIGNED; return false; } } public static class StringMatcher { private static final char MAX_UNICODE = '\uFFFF'; private final Collator mCollator; StringMatcher() { // On android N and above, Collator uses ICU implementation which has a much better // support for non-latin locales. mCollator = Collator.getInstance(); mCollator.setStrength(Collator.PRIMARY); mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); } /** * Returns true if {@param query} is a prefix of {@param target} */ public boolean matches(String query, String target) { switch (mCollator.compare(query, target)) { case 0: return true; case -1: // The target string can contain a modifier which would make it larger than // the query string (even though the length is same). If the query becomes // larger after appending a unicode character, it was originally a prefix of // the target string and hence should match. return mCollator.compare(query + MAX_UNICODE, target) > -1; default: return false; } } public static StringMatcher getInstance() { return new StringMatcher(); } } }
tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java +20 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.ComponentName; import android.test.InstrumentationTestCase; import com.android.launcher3.AppInfo; import com.android.launcher3.Utilities; import java.util.ArrayList; import java.util.List; Loading Loading @@ -75,6 +76,25 @@ public class DefaultAppSearchAlgorithmTest extends InstrumentationTestCase { assertTrue(mAlgorithm.matches(getInfo("电子邮件"), "电子")); assertFalse(mAlgorithm.matches(getInfo("电子邮件"), "子")); assertFalse(mAlgorithm.matches(getInfo("电子邮件"), "邮件")); assertFalse(mAlgorithm.matches(getInfo("Bot"), "ba")); assertFalse(mAlgorithm.matches(getInfo("bot"), "ba")); } public void testMatchesVN() { if (!Utilities.ATLEAST_NOUGAT) { return; } assertTrue(mAlgorithm.matches(getInfo("다운로드"), "다")); assertTrue(mAlgorithm.matches(getInfo("드라이브"), "드")); assertTrue(mAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ")); assertTrue(mAlgorithm.matches(getInfo("운로 드라이브"), "ㄷ")); assertTrue(mAlgorithm.matches(getInfo("abc"), "åbç")); assertTrue(mAlgorithm.matches(getInfo("Alpha"), "ål")); assertFalse(mAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷㄷ")); assertFalse(mAlgorithm.matches(getInfo("로드라이브"), "ㄷ")); assertFalse(mAlgorithm.matches(getInfo("abc"), "åç")); } private AppInfo getInfo(String title) { Loading