Add feature flag to control whether to inject fallback app results when AiAi fails to return app corpus results.

Also add simple prefix matching method.

Bug: 255355348
Test: manual
Change-Id: I35f2089924d774f3bcc1c05638a5d6b45ed7443f
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index acab52b..c66f3a1 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -24,8 +24,8 @@
 public class StringMatcherUtility {
 
     /**
-     * Returns {@code true} is {@code query} is a prefix substring of a complete word/phrase in
-     * {@code target}.
+     * Returns {@code true} if {@code query} is a prefix of a substring in {@code target}. How to
+     * break target to valid substring is defined in the given {@code matcher}.
      */
     public static boolean matches(String query, String target, StringMatcher matcher) {
         int queryLength = query.length();
@@ -50,7 +50,7 @@
             thisType = nextType;
             nextType = i < (targetLength - 1)
                     ? Character.getType(target.codePointAt(i + 1)) : Character.UNASSIGNED;
-            if (isBreak(thisType, lastType, nextType)
+            if (matcher.isBreak(thisType, lastType, nextType)
                     && matcher.matches(query, target.substring(i, i + queryLength))) {
                 return true;
             }
@@ -59,52 +59,6 @@
     }
 
     /**
-     * Returns true if the current point should be a break point. Following cases
-     * are considered as break points:
-     *      1) Any non space character after a space character
-     *      2) Any digit after a non-digit character
-     *      3) Any capital character after a digit or small character
-     *      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) {
-                    return true;
-                }
-                // Follow through
-            case Character.TITLECASE_LETTER:
-                // Break point if previous was not a upper case
-                return prevType != Character.UPPERCASE_LETTER;
-            case Character.LOWERCASE_LETTER:
-                // Break point if previous was not a letter.
-                return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
-            case Character.DECIMAL_DIGIT_NUMBER:
-            case Character.LETTER_NUMBER:
-            case Character.OTHER_NUMBER:
-                // Break point if previous was not a number
-                return !(prevType == Character.DECIMAL_DIGIT_NUMBER
-                        || prevType == Character.LETTER_NUMBER
-                        || prevType == Character.OTHER_NUMBER);
-            case Character.MATH_SYMBOL:
-            case Character.CURRENCY_SYMBOL:
-            case Character.OTHER_PUNCTUATION:
-            case Character.DASH_PUNCTUATION:
-                // Always a break point for a symbol
-                return true;
-            default:
-                return  false;
-        }
-    }
-
-    /**
      * Performs locale sensitive string comparison using {@link Collator}.
      */
     public static class StringMatcher {
@@ -142,6 +96,75 @@
         public static StringMatcher getInstance() {
             return new StringMatcher();
         }
+
+        /**
+         * Returns true if the current point should be a break point.
+         *
+         * Following cases are considered as break points:
+         *     1) Any non space character after a space character
+         *     2) Any digit after a non-digit character
+         *     3) Any capital character after a digit or small character
+         *     4) Any capital character before a small character
+         *
+         * E.g., "YouTube" matches the input "you" and "tube", but not "out".
+         */
+        protected 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) {
+                        return true;
+                    }
+                    // Follow through
+                case Character.TITLECASE_LETTER:
+                    // Break point if previous was not a upper case
+                    return prevType != Character.UPPERCASE_LETTER;
+                case Character.LOWERCASE_LETTER:
+                    // Break point if previous was not a letter.
+                    return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
+                case Character.DECIMAL_DIGIT_NUMBER:
+                case Character.LETTER_NUMBER:
+                case Character.OTHER_NUMBER:
+                    // Break point if previous was not a number
+                    return !(prevType == Character.DECIMAL_DIGIT_NUMBER
+                            || prevType == Character.LETTER_NUMBER
+                            || prevType == Character.OTHER_NUMBER);
+                case Character.MATH_SYMBOL:
+                case Character.CURRENCY_SYMBOL:
+                case Character.OTHER_PUNCTUATION:
+                case Character.DASH_PUNCTUATION:
+                    // Always a break point for a symbol
+                    return true;
+                default:
+                    return  false;
+            }
+        }
+    }
+
+    /**
+     * Subclass of {@code StringMatcher} using simple space break for prefix matching.
+     * E.g., "YouTube" matches the input "you". "Play Store" matches the input "play".
+     */
+    public static class StringMatcherSpace extends StringMatcher {
+
+        public static StringMatcherSpace getInstance() {
+            return new StringMatcherSpace();
+        }
+
+        /**
+         * The first character or any character after a space is considered as a break point.
+         * Returns true if the current point should be a break point.
+         */
+        @Override
+        protected boolean isBreak(int thisType, int prevType, int nextType) {
+            return prevType == Character.UNASSIGNED || prevType == Character.SPACE_SEPARATOR;
+        }
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
index 413f404..3b53255 100644
--- a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -24,6 +24,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+import com.android.launcher3.search.StringMatcherUtility.StringMatcherSpace;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,11 +35,12 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class StringMatcherUtilityTest {
-    private static final StringMatcher MATCHER =
-            StringMatcher.getInstance();
+    private static final StringMatcher MATCHER = StringMatcher.getInstance();
+    private static final StringMatcherSpace MATCHER_SPACE = StringMatcherSpace.getInstance();
 
     @Test
     public void testMatches() {
+        assertTrue(matches("white", "white cow", MATCHER));
         assertTrue(matches("white ", "white cow", MATCHER));
         assertTrue(matches("white c", "white cow", MATCHER));
         assertTrue(matches("cow", "white cow", MATCHER));
@@ -93,4 +95,47 @@
         assertFalse(matches("ㄷ", "로드라이브", MATCHER));
         assertFalse(matches("åç", "abc", MATCHER));
     }
+
+    @Test
+    public void testMatchesWithSpaceBreakOnly() {
+        assertTrue(matches("white", "white cow", MATCHER_SPACE));
+        assertTrue(matches("white ", "white cow", MATCHER_SPACE));
+        assertTrue(matches("white c", "white cow", MATCHER_SPACE));
+        assertTrue(matches("cow", "white cow", MATCHER_SPACE));
+        assertTrue(matches("cow", "whitecow cow", MATCHER_SPACE));
+
+        assertFalse(matches("cow", "whiteCow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whiteCOW", MATCHER_SPACE));
+        assertFalse(matches("cow", "whitecowCOW", MATCHER_SPACE));
+        assertFalse(matches("cow", "white2cow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whitecow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whitEcow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whitecowCow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whitecowcow", MATCHER_SPACE));
+        assertFalse(matches("cow", "whit ecowcow", MATCHER_SPACE));
+
+        assertFalse(matches("dog", "cats&dogs", MATCHER_SPACE));
+        assertFalse(matches("dog", "cats&Dogs", MATCHER_SPACE));
+        assertFalse(matches("&", "cats&Dogs", MATCHER_SPACE));
+
+        assertFalse(matches("43", "2+43", MATCHER_SPACE));
+        assertFalse(matches("3", "2+43", MATCHER_SPACE));
+
+        assertTrue(matches("q", "Q", MATCHER_SPACE));
+        assertTrue(matches("q", "  Q", MATCHER_SPACE));
+
+        // match lower case words
+        assertTrue(matches("e", "elephant", MATCHER_SPACE));
+        assertTrue(matches("eL", "Elephant", MATCHER_SPACE));
+
+        assertTrue(matches("电", "电子邮件", MATCHER_SPACE));
+        assertTrue(matches("电子", "电子邮件", MATCHER_SPACE));
+        assertTrue(matches("子", "电子邮件", MATCHER_SPACE));
+        assertTrue(matches("邮件", "电子邮件", MATCHER_SPACE));
+
+        assertFalse(matches("ba", "Bot", MATCHER_SPACE));
+        assertFalse(matches("ba", "bot", MATCHER_SPACE));
+        assertFalse(matches("phant", "elephant", MATCHER_SPACE));
+        assertFalse(matches("elephants", "elephant", MATCHER_SPACE));
+    }
 }