Merge "PopupContainerWithArrow should align correctly in RTL mode Test: see attached screenshot in the bug" into ub-launcher3-dorval-polish
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index e836d7d..b211207 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -17,7 +17,6 @@
 <resources>
     <!-- All Apps -->
     <dimen name="all_apps_button_scale_down">8dp</dimen>
-    <dimen name="all_apps_search_bar_height">54dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">64dp</dimen>
     <dimen name="all_apps_empty_search_bg_top_offset">180dp</dimen>
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ca5d8e7..2d5f8d0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -436,7 +436,8 @@
         // Since we are only concerned with the overall padding, layout direction does
         // not matter.
         Point padding = getTotalWorkspacePadding();
-        result.x = calculateCellWidth(availableWidthPx - padding.x, inv.numColumns);
+        int cellPadding = cellLayoutPaddingLeftRightPx * 2;
+        result.x = calculateCellWidth(availableWidthPx - padding.x - cellPadding, inv.numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.y, inv.numRows);
         return result;
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fe459bb..c2bddd5 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3049,7 +3049,6 @@
             List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
             if (apps != null) {
                 mAppsView.setPredictedApps(apps);
-                getUserEventDispatcher().setPredictedApps(apps);
             }
         }
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0d7e4fa..f781a3d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1401,7 +1401,7 @@
     @Override
     protected boolean shouldFlingForVelocity(int velocityX) {
         // When the overlay is moving, the fling or settle transition is controlled by the overlay.
-        return Float.compare(mOverlayTranslation, 0) == 0 &&
+        return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
                 super.shouldFlingForVelocity(velocityX);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index ccef4f2..2095192 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -359,7 +359,7 @@
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        targetParent.containerType = mAppsRecyclerView.getContainerType(v);
+        // This is filled in {@link AllAppsRecyclerView}
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index fb785fb..2514458 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -30,18 +30,21 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 import java.util.List;
 
 /**
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
-public class AllAppsRecyclerView extends BaseRecyclerView {
+public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
 
     private AlphabeticalAppsList mApps;
     private AllAppsFastScrollHelper mFastScrollHelper;
@@ -231,9 +234,10 @@
         updateEmptySearchBackgroundBounds();
     }
 
-    public int getContainerType(View v) {
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         if (mApps.hasFilter()) {
-            return ContainerType.SEARCHRESULT;
+            targetParent.containerType = ContainerType.SEARCHRESULT;
         } else {
             if (v instanceof BubbleTextView) {
                 BubbleTextView icon = (BubbleTextView) v;
@@ -242,11 +246,13 @@
                     List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
                     AlphabeticalAppsList.AdapterItem item = items.get(position);
                     if (item.viewType == AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON) {
-                        return ContainerType.PREDICTION;
+                        targetParent.containerType = ContainerType.PREDICTION;
+                        target.predictedRank = item.rowAppIndex;
+                        return;
                     }
                 }
             }
-            return ContainerType.ALLAPPS;
+            targetParent.containerType = ContainerType.ALLAPPS;
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 5cb12d5..39e2088 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -202,7 +202,8 @@
                 if (!dp.isVerticalBarLayout()) {
                     Rect insets = mLauncher.getDragLayer().getInsets();
                     int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
-                    int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight);
+                    int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
+                            + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
                     listener.onScrollRangeChanged(hotseatBottom - searchTopMargin);
                 } else {
                     listener.onScrollRangeChanged(bottom);
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 4303302..21eb3fb 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -20,6 +20,7 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.util.ComponentKey;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -61,8 +62,9 @@
         // 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());
             }
         }
@@ -70,6 +72,10 @@
     }
 
     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();
@@ -90,7 +96,7 @@
             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;
             }
         }
@@ -106,6 +112,13 @@
      *      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) {
@@ -132,8 +145,44 @@
                 // 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();
         }
     }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 622cd10..85792d4 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -792,6 +792,7 @@
             mFolderIcon.setVisibility(View.VISIBLE);
             if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
                 mFolderIcon.setBackgroundVisible(true);
+                mFolderIcon.mFolderName.setTextVisibility(true);
             }
             if (wasAnimated) {
                 if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
@@ -1358,8 +1359,11 @@
         }
         mContent.completePendingPageChanges();
 
-        if (d.dragInfo instanceof PendingAddShortcutInfo) {
-            PendingAddShortcutInfo pasi = (PendingAddShortcutInfo) d.dragInfo;
+        PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
+                ? (PendingAddShortcutInfo) d.dragInfo : null;
+        ShortcutInfo pasiSi = pasi != null ? pasi.activityInfo.createShortcutInfo() : null;
+        if (pasi != null && pasiSi == null) {
+            // There is no ShortcutInfo, so we have to go through a configuration activity.
             pasi.container = mInfo.id;
             pasi.rank = mEmptyCellRank;
 
@@ -1369,7 +1373,9 @@
             mRearrangeOnClose = true;
         } else {
             final ShortcutInfo si;
-            if (d.dragInfo instanceof AppInfo) {
+            if (pasiSi != null) {
+                si = pasiSi;
+            } else if (d.dragInfo instanceof AppInfo) {
                 // Came from all apps -- make a copy.
                 si = ((AppInfo) d.dragInfo).makeShortcut();
             } else {
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 7a05f67..bb23207 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -61,7 +61,7 @@
     static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
     private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
 
-    private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY = 200;
+    private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY = 100;
     private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION = 300;
     private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
 
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index e28a97a..d5c6515 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -36,10 +36,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LogConfig;
 
-import java.util.List;
 import java.util.Locale;
 import java.util.UUID;
 
@@ -128,9 +126,6 @@
     private boolean mIsInLandscapeMode;
     private String mUuidStr;
 
-    // Used for filling in predictedRank on {@link Target}s.
-    private List<ComponentKey> mPredictedApps;
-
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
     // packageNameHash      required    optional    required
@@ -138,32 +133,11 @@
     // intentHash                       required
     // --------------------------------------------------------------
 
-    protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
-        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v), newTarget(Target.Type.CONTAINER));
-
-        // TODO: make idx percolate up the view hierarchy if needed.
-        int idx = 0;
-        if (fillInLogContainerData(event, v)) {
-            ItemInfo itemInfo = (ItemInfo) v.getTag();
-            event.srcTarget[idx].intentHash = intentHashCode;
-            if (cn != null) {
-                event.srcTarget[idx].packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
-                event.srcTarget[idx].componentHash = (mUuidStr + cn.flattenToString()).hashCode();
-                if (mPredictedApps != null) {
-                    event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
-                            new ComponentKey(cn, itemInfo.user));
-                }
-            }
-        }
-        return event;
-    }
-
     /**
      * Fills in the container data on the given event if the given view is not null.
      * @return whether container data was added.
      */
-    private boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
+    protected boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
         // Fill in grid(x,y), pageIndex of the child and container type of the parent
         LogContainerProvider provider = getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
@@ -175,20 +149,31 @@
     }
 
     public void logAppLaunch(View v, Intent intent) {
-        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
-        if (ev == null) {
-            return;
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+
+        if (fillInLogContainerData(event, v)) {
+            fillIntentInfo(event.srcTarget[0], intent);
         }
-        dispatchUserEvent(ev, intent);
+        dispatchUserEvent(event, intent);
+    }
+
+    protected void fillIntentInfo(Target target, Intent intent) {
+        target.intentHash = intent.hashCode();
+        ComponentName cn = intent.getComponent();
+        if (cn != null) {
+            target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
+            target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
+        }
     }
 
     public void logNotificationLaunch(View v, PendingIntent intent) {
-        ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
-        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
-        if (ev == null) {
-            return;
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+        if (fillInLogContainerData(event, v)) {
+            event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
         }
-        dispatchUserEvent(ev, null);
+        dispatchUserEvent(event, null);
     }
 
     public void logActionCommand(int command, int containerType) {
@@ -273,10 +258,6 @@
         resetElapsedContainerMillis();
     }
 
-    public void setPredictedApps(List<ComponentKey> predictedApps) {
-        mPredictedApps = predictedApps;
-    }
-
     /* Currently we are only interested in whether this event happens or not and don't
     * care about which screen moves to where. */
     public void logOverviewReorder() {
@@ -348,7 +329,10 @@
     }
 
     private static String getTargetsStr(Target[] targets) {
-        return "child:" + LoggerUtils.getTargetStr(targets[0]) +
-                (targets.length > 1 ? "\tparent:" + LoggerUtils.getTargetStr(targets[1]) : "");
+        String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
+        for (int i = 1; i < targets.length; i++) {
+            result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
+        }
+        return result;
     }
 }
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
index 6b992fc..29834d7 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
@@ -21,7 +21,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dynamicui.ExtractedColors;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
 
 /**
  * A PageIndicator that briefly shows a fraction of a line when moving between pages.
@@ -128,6 +130,10 @@
         mLauncher = Launcher.getLauncher(context);
         mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
         setCaretDrawable(new CaretDrawable(context));
+
+        boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
+        mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
+        mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
     }
 
     @Override
@@ -219,6 +225,9 @@
      * - mostly opaque black if the hotseat is black (ignoring alpha)
      */
     public void updateColor(ExtractedColors extractedColors) {
+        if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
+            return;
+        }
         int originalLineAlpha = mLinePaint.getAlpha();
         int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX);
         if (color != Color.TRANSPARENT) {
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index e8f13a1..62b6903 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.widget;
 
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
index 58dc0c4..26ec69b 100644
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
@@ -19,6 +19,7 @@
 import android.test.InstrumentationTestCase;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.Utilities;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -75,6 +76,25 @@
         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) {