Merge "Handling configuration changes at runtime instead of killing the process" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index 2608280..4b32702 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,3 +28,23 @@
     ],
     platform_apis: true,
 }
+
+
+android_library {
+    name: "icon-loader",
+    sdk_version: "28",
+    static_libs: [
+        "androidx.core_core",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/com/android/launcher3/icons/BaseIconFactory.java",
+        "src/com/android/launcher3/icons/BitmapInfo.java",
+        "src/com/android/launcher3/icons/IconNormalizer.java",
+        "src/com/android/launcher3/icons/FixedScaleDrawable.java",
+        "src/com/android/launcher3/icons/ShadowGenerator.java",
+        "src/com/android/launcher3/icons/ColorExtractor.java",
+    ],
+}
diff --git a/Android.mk b/Android.mk
index 15daf1f..fbe19b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -66,7 +66,8 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.recyclerview_recyclerview \
-    androidx.dynamicanimation_dynamicanimation
+    androidx.dynamicanimation_dynamicanimation \
+    androidx.preference_preference
 
 LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
 
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 8f4d5be..1beaea5 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -122,7 +122,7 @@
                        android:value="true" />
 
         <activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
-            android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+            android:theme="@style/AppItemActivityTheme"
             android:excludeFromRecents="true"
             android:autoRemoveFromRecents="true"
             android:label="@string/action_add_to_workspace" >
@@ -156,7 +156,7 @@
         The settings activity. To extend point settings_fragment_name to appropriate fragment class
         -->
         <activity
-            android:name="com.android.launcher3.SettingsActivity"
+            android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
             android:theme="@android:style/Theme.DeviceDefault.Settings"
             android:autoRemoveFromRecents="true">
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 41dd0bd..b3ed365 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -112,6 +112,7 @@
   CANCEL_TARGET = 14;
   TASK_PREVIEW = 15;
   SPLIT_SCREEN_TARGET = 16;
+  REMOTE_ACTION_SHORTCUT = 17;
 }
 
 enum TipType {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 7289516..59a937f 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -20,21 +20,34 @@
 import android.graphics.Matrix;
 import android.view.View;
 
+import androidx.annotation.AnyThread;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
-import androidx.annotation.AnyThread;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Factory class to create and add an overlays on the TaskView
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
-
     private static TaskOverlayFactory sInstance;
 
+    /** Note that these will be shown in order from top to bottom, if available for the task. */
+    private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
+            new TaskSystemShortcut.AppInfo(),
+            new TaskSystemShortcut.SplitScreen(),
+            new TaskSystemShortcut.Pin(),
+            new TaskSystemShortcut.Install(),
+    };
+
     public static TaskOverlayFactory get(Context context) {
         Preconditions.assertUIThread();
         if (sInstance == null) {
@@ -55,9 +68,23 @@
 
     public static class TaskOverlay {
 
-        public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+        public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) {
+        }
 
-        public void reset() { }
+        public void reset() {
+        }
 
+        public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
+            final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
+            final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
+            for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
+                View.OnClickListener onClickListener =
+                        menuOption.getOnClickListener(activity, taskView);
+                if (onClickListener != null) {
+                    shortcuts.add(menuOption);
+                }
+            }
+            return shortcuts;
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 66ce4c3..a8eb321 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -63,7 +63,7 @@
 
     protected T mSystemShortcut;
 
-    protected TaskSystemShortcut(T systemShortcut) {
+    public TaskSystemShortcut(T systemShortcut) {
         super(systemShortcut);
         mSystemShortcut = systemShortcut;
     }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index c4c660c..1c79f44 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -540,7 +540,7 @@
     public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
         displacement = -displacement;
-        if (displacement > mTransitionDragLength) {
+        if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
             mCurrentShift.updateValue(1);
 
             if (!mBgLongSwipeMode) {
@@ -815,8 +815,12 @@
         long startMillis = SystemClock.uptimeMillis();
         executeOnUiThread(() -> {
             // Animate the launcher components at the same time as the window, always on UI thread.
-            if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible
-                    && start != end && duration > 0) {
+            if (mLauncherTransitionController == null) {
+                return;
+            }
+            if (start == end || duration <= 0) {
+                mLauncherTransitionController.getAnimationPlayer().end();
+            } else {
                 // Adjust start progress and duration in case we are on a different thread.
                 long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
                 elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index c4afad7..667165b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -43,6 +43,8 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.IconView.OnScaleUpdateListener;
 
+import java.util.List;
+
 /**
  * Contains options for a recent task when long-pressing its icon.
  */
@@ -50,14 +52,6 @@
 
     private static final Rect sTempRect = new Rect();
 
-    /** Note that these will be shown in order from top to bottom, if available for the task. */
-    public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] {
-            new TaskSystemShortcut.AppInfo(),
-            new TaskSystemShortcut.SplitScreen(),
-            new TaskSystemShortcut.Pin(),
-            new TaskSystemShortcut.Install(),
-    };
-
     private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
         @Override
         public void onScaleUpdate(float scale) {
@@ -197,11 +191,13 @@
         params.topMargin = (int) -mThumbnailTopMargin;
         mTaskIcon.setLayoutParams(params);
 
-        for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
-            OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
-            if (onClickListener != null) {
-                addMenuOption(menuOption, onClickListener);
-            }
+        final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
+        final List<TaskSystemShortcut> shortcuts =
+                taskView.getTaskOverlay().getEnabledShortcuts(taskView);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
+            addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 7223f97..ce65de1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -173,6 +173,10 @@
         return 0;
     }
 
+    public TaskOverlay getTaskOverlay() {
+        return mOverlay;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c1424c4..56074f0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -42,12 +42,12 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -56,8 +56,9 @@
 import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-
 import com.android.systemui.shared.system.ActivityOptionsCompat;
+
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -190,6 +191,10 @@
         return mIconView;
     }
 
+    public TaskOverlayFactory.TaskOverlay getTaskOverlay() {
+        return mSnapshotView.getTaskOverlay();
+    }
+
     public void launchTask(boolean animate) {
         launchTask(animate, (result) -> {
             if (!result) {
@@ -384,7 +389,11 @@
 
         final Context context = getContext();
         final BaseDraggingActivity activity = fromContext(context);
-        for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
+        final List<TaskSystemShortcut> shortcuts =
+                mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
             OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
             if (onClickListener != null) {
                 info.addAction(menuOption.createAccessibilityAction(context));
@@ -407,7 +416,11 @@
             return true;
         }
 
-        for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
+        final List<TaskSystemShortcut> shortcuts =
+                mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
             if (menuOption.hasHandlerForAction(action)) {
                 OnClickListener onClickListener = menuOption.getOnClickListener(
                         fromContext(getContext()), this);
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 6c316e6..830255b 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -44,7 +44,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:background="?android:attr/colorPrimaryDark"
-                android:theme="@style/WidgetContainerTheme">
+                android:theme="?attr/widgetsTheme">
 
                 <com.android.launcher3.dragndrop.LivePreviewWidgetCell
                     android:id="@+id/widget_cell"
diff --git a/res/values-v19/styles.xml b/res/values-night-v26/styles.xml
similarity index 70%
copy from res/values-v19/styles.xml
copy to res/values-night-v26/styles.xml
index 36c0971..510e1f4 100644
--- a/res/values-v19/styles.xml
+++ b/res/values-night-v26/styles.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-* Copyright (C) 2016 The Android Open Source Project
+* Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
 * limitations under the License.
 */
 -->
+
 <resources>
 
-    <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
-        <item name="android:windowTranslucentStatus">true</item>
-        <item name="android:windowTranslucentNavigation">true</item>
+    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
     </style>
 
 </resources>
\ No newline at end of file
diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml
deleted file mode 100644
index 927719c..0000000
--- a/res/values-v21/styles.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<resources>
-
-    <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
-        <item name="android:windowTranslucentStatus">false</item>
-        <item name="android:windowTranslucentNavigation">false</item>
-        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
-        <item name="android:statusBarColor">#00000000</item>
-        <item name="android:navigationBarColor">#00000000</item>
-    </style>
-</resources>
diff --git a/res/values-v19/styles.xml b/res/values-v22/styles.xml
similarity index 71%
rename from res/values-v19/styles.xml
rename to res/values-v22/styles.xml
index 36c0971..f86db7a 100644
--- a/res/values-v19/styles.xml
+++ b/res/values-v22/styles.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-* Copyright (C) 2016 The Android Open Source Project
+* Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
 * limitations under the License.
 */
 -->
+
 <resources>
 
-    <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
-        <item name="android:windowTranslucentStatus">true</item>
-        <item name="android:windowTranslucentNavigation">true</item>
+    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
 </resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0efaccf..85c2e65 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -123,6 +123,7 @@
     <item type="id" name="action_deep_shortcuts" />
     <item type="id" name="action_shortcuts_and_notifications"/>
     <item type="id" name="action_dismiss_notification" />
+    <item type="id" name="action_remote_action_shortcut" />
 
 <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eb6b284..7e5784d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -347,4 +347,7 @@
     <string name="work_mode_off_label">Notifications and apps are off</string>
     <string name="bottom_work_tab_user_education_close_button">Close</string>
     <string name="bottom_work_tab_user_education_closed">Closed</string>
+
+    <!-- Failed action error message: e.g. Failed: Pause -->
+    <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 098aac5..7b11891 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -27,7 +27,7 @@
         <item name="android:colorEdgeEffect">#FF757575</item>
     </style>
 
-    <style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
+    <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
         <item name="allAppsScrimColor">#EAFFFFFF</item>
         <item name="allAppsInterimScrimAlpha">46</item>
         <item name="allAppsNavBarScrimColor">#66FFFFFF</item>
@@ -44,9 +44,13 @@
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
         <item name="folderBadgeColor">?android:attr/colorPrimary</item>
         <item name="loadingIconColor">#FFF</item>
-    </style>
 
-    <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs"></style>
+        <item name="android:windowTranslucentStatus">false</item>
+        <item name="android:windowTranslucentNavigation">false</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">#00000000</item>
+        <item name="android:navigationBarColor">#00000000</item>
+    </style>
 
     <style name="LauncherTheme.DarkText" parent="@style/LauncherTheme">
         <item name="workspaceTextColor">#FF212121</item>
@@ -94,6 +98,10 @@
     <style name="AppTheme.Dark" parent="@style/LauncherTheme.Dark" />
     <style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
 
+    <style name="AppItemActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
+        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+    </style>
+
     <!--
     Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
     Various foreground colors are overridden to be workspaceTextColor so that they are properly
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 1df7c2f..c55cc49 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -14,9 +14,10 @@
      limitations under the License.
 -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<androidx.preference.PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <com.android.launcher3.views.ButtonPreference
+    <com.android.launcher3.settings.IconBadgingPreference
         android:key="pref_icon_badging"
         android:title="@string/icon_badging_title"
         android:persistent="false"
@@ -27,7 +28,7 @@
                 android:name=":settings:fragment_args_key"
                 android:value="notification_badging" />
         </intent>
-    </com.android.launcher3.views.ButtonPreference>
+    </com.android.launcher3.settings.IconBadgingPreference>
 
     <SwitchPreference
         android:key="pref_add_icon_to_home"
@@ -52,10 +53,10 @@
         android:defaultValue=""
         android:persistent="false" />
 
-    <PreferenceScreen
+    <androidx.preference.PreferenceScreen
         android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
         android:key="flag_toggler"
         android:persistent="false"
         android:title="Feature flags"/>
 
-</PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 7190f12..851454b 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -480,7 +480,7 @@
                 final LauncherAppState app = LauncherAppState.getInstance(mContext);
                 // Set default values until proper values is loaded.
                 appInfo.title = "";
-                app.getIconCache().getDefaultIcon(user).applyTo(appInfo);
+                appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
                 final ShortcutInfo si = appInfo.makeShortcut();
                 if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
                     app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
@@ -495,7 +495,7 @@
             } else if (shortcutInfo != null) {
                 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
                 LauncherIcons li = LauncherIcons.obtain(mContext);
-                li.createShortcutIcon(shortcutInfo).applyTo(si);
+                si.applyFrom(li.createShortcutIcon(shortcutInfo));
                 li.recycle();
                 return Pair.create((ItemInfo) si, (Object) shortcutInfo);
             } else if (providerInfo != null) {
@@ -656,7 +656,7 @@
         if (iconInfo == null) {
             iconInfo = app.getIconCache().getDefaultIcon(info.user);
         }
-        iconInfo.applyTo(info);
+        info.applyFrom(iconInfo);
 
         info.title = Utilities.trim(name);
         info.contentDescription = UserManagerCompat.getInstance(app.getContext())
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 6d453c9..e29f927 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -20,6 +20,8 @@
 
 import android.graphics.Bitmap;
 
+import com.android.launcher3.icons.BitmapInfo;
+
 /**
  * Represents an ItemInfo which also holds an icon.
  */
@@ -118,4 +120,10 @@
     public boolean usingLowResIcon() {
         return iconBitmap == LOW_RES_ICON;
     }
+
+    public void applyFrom(BitmapInfo info) {
+        iconBitmap = info.icon;
+        iconColor = info.color;
+    }
+
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0395fbb..e714a0b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -110,7 +110,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -137,6 +136,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -2163,11 +2163,11 @@
     }
 
     /**
-     * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
+     * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
      * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
      */
     @Override
-    public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+    public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index cbfde25..b3dabae 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,7 +57,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Provider;
@@ -69,6 +68,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -161,7 +161,7 @@
         public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
-        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
+        public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
     }
 
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
@@ -657,7 +657,7 @@
         updateAndBindShortcutInfo(() -> {
             si.updateFromDeepShortcutInfo(info, mApp.getContext());
             LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
-            li.createShortcutIcon(info).applyTo(si);
+            si.applyFrom(li.createShortcutIcon(info));
             li.recycle();
             return si;
         });
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index a18dfde..a253893 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderShape;
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,6 @@
         FeatureFlags.initialize(context);
         IconShapeOverride.apply(context);
         SessionCommitReceiver.applyDefaultUserPrefs(context);
+        FolderShape.init();
     }
 }
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
deleted file mode 100644
index a17f614..0000000
--- a/src/com/android/launcher3/SettingsActivity.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
-import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.Adapter;
-import android.widget.ListView;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.IconShapeOverride;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.ListViewHighlighter;
-import com.android.launcher3.util.SecureSettingsObserver;
-import com.android.launcher3.views.ButtonPreference;
-
-import java.util.Objects;
-
-/**
- * Settings activity for Launcher. Currently implements the following setting: Allow rotation
- */
-public class SettingsActivity extends Activity
-        implements PreferenceFragment.OnPreferenceStartFragmentCallback {
-
-    private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
-
-    private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
-    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
-    private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
-
-    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-    private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
-    private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
-    private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        if (savedInstanceState == null) {
-            Fragment f = Fragment.instantiate(this, getString(R.string.settings_fragment_name));
-            // Display the fragment as the main content.
-            getFragmentManager().beginTransaction()
-                    .replace(android.R.id.content, f)
-                    .commit();
-        }
-    }
-
-    protected PreferenceFragment getNewFragment() {
-        return new LauncherSettingsFragment();
-    }
-
-    @Override
-    public boolean onPreferenceStartFragment(
-            PreferenceFragment preferenceFragment, Preference pref) {
-        if (getFragmentManager().isStateSaved()) {
-            // Sometimes onClick can come after onPause because of being posted on the handler.
-            // Skip starting new fragments in that case.
-            return false;
-        }
-        Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
-        if (f instanceof DialogFragment) {
-            ((DialogFragment) f).show(getFragmentManager(), pref.getKey());
-        } else {
-            getFragmentManager()
-                    .beginTransaction()
-                    .replace(android.R.id.content, f)
-                    .addToBackStack(pref.getKey())
-                    .commit();
-        }
-        return true;
-    }
-
-    /**
-     * This fragment shows the launcher preferences.
-     */
-    public static class LauncherSettingsFragment extends PreferenceFragment {
-
-        private SecureSettingsObserver mIconBadgingObserver;
-
-        private String mPreferenceKey;
-        private boolean mPreferenceHighlighted = false;
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
-            }
-
-            getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
-            addPreferencesFromResource(R.xml.launcher_preferences);
-
-            // Only show flag toggler UI if this build variant implements that.
-            Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
-            if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
-                getPreferenceScreen().removePreference(flagToggler);
-            }
-
-            ContentResolver resolver = getActivity().getContentResolver();
-
-            ButtonPreference iconBadgingPref =
-                    (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
-            if (!Utilities.ATLEAST_OREO) {
-                getPreferenceScreen().removePreference(
-                        findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
-                getPreferenceScreen().removePreference(iconBadgingPref);
-            } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) {
-                getPreferenceScreen().removePreference(iconBadgingPref);
-            } else {
-                // Listen to system notification badge settings while this UI is active.
-                mIconBadgingObserver = newNotificationSettingsObserver(
-                        getActivity(), new IconBadgingObserver(iconBadgingPref, resolver));
-                mIconBadgingObserver.register();
-                // Also listen if notification permission changes
-                mIconBadgingObserver.getResolver().registerContentObserver(
-                        Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
-                        mIconBadgingObserver);
-                mIconBadgingObserver.dispatchOnChange();
-            }
-
-            Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
-            if (iconShapeOverride != null) {
-                if (IconShapeOverride.isSupported(getActivity())) {
-                    IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);
-                } else {
-                    getPreferenceScreen().removePreference(iconShapeOverride);
-                }
-            }
-
-            // Setup allow rotation preference
-            Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY);
-            if (getResources().getBoolean(R.bool.allow_rotation)) {
-                // Launcher supports rotation by default. No need to show this setting.
-                getPreferenceScreen().removePreference(rotationPref);
-            } else {
-                // Initialize the UI once
-                rotationPref.setDefaultValue(getAllowRotationDefaultValue());
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
-        }
-
-        @Override
-        public void onResume() {
-            super.onResume();
-
-            Intent intent = getActivity().getIntent();
-            mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
-            if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) {
-                getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS);
-            }
-        }
-
-        private void highlightPreference() {
-            Preference pref = findPreference(mPreferenceKey);
-            if (pref == null || getPreferenceScreen() == null) {
-                return;
-            }
-            PreferenceScreen screen = getPreferenceScreen();
-            if (Utilities.ATLEAST_OREO) {
-                screen = selectPreferenceRecursive(pref, screen);
-            }
-            if (screen == null) {
-                return;
-            }
-
-            View root = screen.getDialog() != null
-                    ? screen.getDialog().getWindow().getDecorView() : getView();
-            ListView list = root.findViewById(android.R.id.list);
-            if (list == null || list.getAdapter() == null) {
-                return;
-            }
-            Adapter adapter = list.getAdapter();
-
-            // Find the position
-            int position = -1;
-            for (int i = adapter.getCount() - 1; i >= 0; i--) {
-                if (pref == adapter.getItem(i)) {
-                    position = i;
-                    break;
-                }
-            }
-            new ListViewHighlighter(list, position);
-            mPreferenceHighlighted = true;
-        }
-
-        @Override
-        public void onDestroy() {
-            if (mIconBadgingObserver != null) {
-                mIconBadgingObserver.unregister();
-                mIconBadgingObserver = null;
-            }
-            super.onDestroy();
-        }
-
-        @TargetApi(Build.VERSION_CODES.O)
-        private PreferenceScreen selectPreferenceRecursive(
-                Preference pref, PreferenceScreen topParent) {
-            if (!(pref.getParent() instanceof PreferenceScreen)) {
-                return null;
-            }
-
-            PreferenceScreen parent = (PreferenceScreen) pref.getParent();
-            if (Objects.equals(parent.getKey(), topParent.getKey())) {
-                return parent;
-            } else if (selectPreferenceRecursive(parent, topParent) != null) {
-                ((PreferenceScreen) parent.getParent())
-                        .onItemClick(null, null, parent.getOrder(), 0);
-                return parent;
-            } else {
-                return null;
-            }
-        }
-    }
-
-    /**
-     * Content observer which listens for system badging setting changes,
-     * and updates the launcher badging setting subtext accordingly.
-     */
-    private static class IconBadgingObserver implements SecureSettingsObserver.OnChangeListener {
-
-        private final ButtonPreference mBadgingPref;
-        private final ContentResolver mResolver;
-
-        public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver) {
-            mBadgingPref = badgingPref;
-            mResolver = resolver;
-        }
-
-        @Override
-        public void onSettingsChanged(boolean enabled) {
-            int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
-
-            boolean serviceEnabled = true;
-            if (enabled) {
-                // Check if the listener is enabled or not.
-                String enabledListeners =
-                        Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS);
-                ComponentName myListener =
-                        new ComponentName(mBadgingPref.getContext(), NotificationListener.class);
-                serviceEnabled = enabledListeners != null &&
-                        (enabledListeners.contains(myListener.flattenToString()) ||
-                                enabledListeners.contains(myListener.flattenToShortString()));
-                if (!serviceEnabled) {
-                    summary = R.string.title_missing_notification_access;
-                }
-            }
-            mBadgingPref.setWidgetFrameVisible(!serviceEnabled);
-            mBadgingPref.setFragment(
-                    serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
-            mBadgingPref.setSummary(summary);
-
-        }
-    }
-
-    public static class NotificationAccessConfirmation
-            extends DialogFragment implements DialogInterface.OnClickListener {
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final Context context = getActivity();
-            String msg = context.getString(R.string.msg_missing_notification_access,
-                    context.getString(R.string.derived_app_name));
-            return new AlertDialog.Builder(context)
-                    .setTitle(R.string.title_missing_notification_access)
-                    .setMessage(msg)
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .setPositiveButton(R.string.title_change_settings, this)
-                    .create();
-        }
-
-        @Override
-        public void onClick(DialogInterface dialogInterface, int i) {
-            ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
-            Bundle showFragmentArgs = new Bundle();
-            showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
-
-            Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
-                    .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
-            getActivity().startActivity(intent);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 11e601c..3f7d68d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -127,8 +127,8 @@
 
     private static final int DEFAULT_PAGE = 0;
 
-    private static final boolean MAP_NO_RECURSE = false;
-    private static final boolean MAP_RECURSE = true;
+    public static final boolean MAP_NO_RECURSE = false;
+    public static final boolean MAP_RECURSE = true;
 
     // The screen id used for the empty screen always present to the right.
     public static final int EXTRA_EMPTY_SCREEN_ID = -201;
@@ -3121,7 +3121,7 @@
      * @param recurse true: iterate over folder children. false: op get the folders themselves.
      * @param op the operator to map over the shortcuts
      */
-    void mapOverItems(boolean recurse, ItemOperator op) {
+    public void mapOverItems(boolean recurse, ItemOperator op) {
         ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
         final int containerCount = containers.size();
         for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index fb44660..82617fe 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -139,7 +139,7 @@
             ShortcutInfo info = new ShortcutInfo(compat, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
             LauncherIcons li = LauncherIcons.obtain(context);
-            li.createShortcutIcon(compat, false /* badged */).applyTo(info);
+            info.applyFrom(li.createShortcutIcon(compat, false /* badged */));
             li.recycle();
             LauncherAppState.getInstance(context).getModel()
                     .updateAndBindShortcutInfo(info, compat);
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 290d8c4..64b5652 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.provider.Settings;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.Keep;
@@ -51,8 +52,10 @@
         throw new UnsupportedOperationException("Don't instantiate BaseFlags");
     }
 
-    public static boolean showFlagTogglerUi() {
-        return Utilities.IS_DEBUG_DEVICE;
+    public static boolean showFlagTogglerUi(Context context) {
+        return Utilities.IS_DEBUG_DEVICE &&
+                Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
+                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
     public static final boolean IS_DOGFOOD_BUILD = false;
@@ -91,8 +94,8 @@
             "APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically");
 
     public static void initialize(Context context) {
-        // Avoid the disk read for builds without the flags UI.
-        if (showFlagTogglerUi()) {
+        // Avoid the disk read for user builds
+        if (Utilities.IS_DEBUG_DEVICE) {
             SharedPreferences sharedPreferences =
                     context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
             synchronized (sLock) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 6d1efd5..8f223a3 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -204,7 +204,7 @@
             public void run() {
                 LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
                 Object[] outObj = new Object[1];
-                final Drawable dr = getFullDrawable(info, appState, outObj);
+                Drawable dr = getFullDrawable(info, appState, outObj);
 
                 if (dr instanceof AdaptiveIconDrawable) {
                     int w = mBitmap.getWidth();
@@ -220,10 +220,20 @@
                     mBadge = getBadge(info, appState, outObj[0]);
                     mBadge.setBounds(badgeBounds);
 
-                    LauncherIcons li = LauncherIcons.obtain(mLauncher);
-                    Utilities.scaleRectAboutCenter(bounds,
-                            li.getNormalizer().getScale(dr, null, null, null));
-                    li.recycle();
+                    // Do not draw the background in case of folder as its translucent
+                    mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+
+                    try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
+                        Drawable nDr; // drawable to be normalized
+                        if (mDrawBitmap) {
+                            nDr = dr;
+                        } else {
+                            // Since we just want the scale, avoid heavy drawing operations
+                            nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
+                        }
+                        Utilities.scaleRectAboutCenter(bounds,
+                                li.getNormalizer().getScale(nDr, null));
+                    }
                     AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
 
                     // Shrink very tiny bit so that the clip path is smaller than the original bitmap
@@ -259,9 +269,6 @@
                             // Assign the variable on the UI thread to avoid race conditions.
                             mScaledMaskPath = mask;
 
-                            // Do not draw the background in case of folder as its translucent
-                            mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
-
                             if (info.isDisabled()) {
                                 FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null);
                                 d.setIsDisabled(true);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c4d1058..94c8d45 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,8 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.text.InputType;
 import android.text.Selection;
@@ -151,6 +153,8 @@
     // Cell ranks used for drag and drop
     @Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank;
 
+    private Path mClipPath;
+
     @ViewDebug.ExportedProperty(category = "launcher",
             mapping = {
                     @ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"),
@@ -1476,4 +1480,25 @@
             sHintText = res.getString(R.string.folder_hint_text);
         }
     }
+
+    /**
+     * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
+     * rounded rect.
+     */
+    public void setClipPath(Path clipPath) {
+        mClipPath = clipPath;
+        invalidate();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mClipPath != null) {
+            int count = canvas.save();
+            canvas.clipPath(mClipPath);
+            super.draw(canvas);
+            canvas.restoreToCount(count);
+        } else {
+            super.draw(canvas);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 1277a20..fa890b9 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderShape.getShape;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -40,7 +41,6 @@
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Themes;
 
@@ -166,7 +166,6 @@
                 Math.round((totalOffsetX + initialSize) / initialScale),
                 Math.round((paddingOffsetY + initialSize) / initialScale));
         Rect endRect = new Rect(0, 0, lp.width, lp.height);
-        float initialRadius = initialSize / initialScale / 2f;
         float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
 
         // Create the animators.
@@ -189,14 +188,8 @@
         play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
         play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
         play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
-        RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider(
-                initialRadius, finalRadius, startRect, endRect) {
-            @Override
-            public boolean shouldRemoveElevationDuringAnimation() {
-                return true;
-            }
-        };
-        play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening));
+        play(a, getShape().createRevealAnimator(
+                mFolder, startRect, endRect, finalRadius, !mIsOpening));
 
         // Animate the elevation midway so that the shadow is not noticeable in the background.
         int midDuration = mDuration / 2;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d09f036..429d44f 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -75,6 +75,7 @@
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
 public class FolderIcon extends FrameLayout implements FolderListener {
+
     @Thunk Launcher mLauncher;
     @Thunk Folder mFolder;
     private FolderInfo mInfo;
@@ -477,20 +478,9 @@
         if (mFolder == null) return;
         if (mFolder.getItemCount() == 0 && !mAnimating) return;
 
-        final int saveCount;
-
-        if (canvas.isHardwareAccelerated()) {
-            saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
-        } else {
-            saveCount = canvas.save();
-            canvas.clipPath(mBackground.getClipPath());
-        }
-
+        final int saveCount = canvas.save();
+        canvas.clipPath(mBackground.getClipPath());
         mPreviewItemManager.draw(canvas);
-
-        if (canvas.isHardwareAccelerated()) {
-            mBackground.clipCanvasHardware(canvas);
-        }
         canvas.restoreToCount(saveCount);
 
         if (!mBackground.drawingDelegated()) {
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
new file mode 100644
index 0000000..ae279cb
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.folder;
+
+import static com.android.launcher3.Workspace.MAP_NO_RECURSE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.FloatArrayEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.RegionIterator;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.view.ViewOutlineProvider;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+
+/**
+ * Abstract representation of the shape of a folder icon
+ */
+public abstract class FolderShape {
+
+    private static FolderShape sInstance = new Circle();
+
+    public static FolderShape getShape() {
+        return sInstance;
+    }
+
+    private static FolderShape[] getAllShapes() {
+        return new FolderShape[] {
+                new Circle(),
+                new RoundedSquare(8f / 50),  // Ratios based on path defined in config_icon_mask
+                new RoundedSquare(30f / 50),
+                new Square(),
+                new TearDrop(),
+                new Squircle()};
+    }
+
+    public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
+            Paint paint);
+
+    public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
+
+    public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+            float endRadius, boolean isReversed);
+
+    /**
+     * Abstract shape where the reveal animation is a derivative of a round rect animation
+     */
+    private static abstract class SimpleRectShape extends FolderShape {
+
+        @Override
+        public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+                float endRadius, boolean isReversed) {
+            return new RoundedRectRevealOutlineProvider(
+                    getStartRadius(startRect), endRadius, startRect, endRect) {
+                @Override
+                public boolean shouldRemoveElevationDuringAnimation() {
+                    return true;
+                }
+            }.createRevealAnimator(target, isReversed);
+        }
+
+        protected abstract float getStartRadius(Rect startRect);
+    }
+
+    /**
+     * Abstract shape which draws using {@link Path}
+     */
+    private static abstract class PathShape extends FolderShape {
+
+        private final Path mTmpPath = new Path();
+
+        @Override
+        public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
+                Paint paint) {
+            mTmpPath.reset();
+            addShape(mTmpPath, offsetX, offsetY, radius);
+            canvas.drawPath(mTmpPath, paint);
+        }
+
+        protected abstract AnimatorUpdateListener newUpdateListener(
+                Rect startRect, Rect endRect, float endRadius, Path outPath);
+
+        @Override
+        public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+                float endRadius, boolean isReversed) {
+            Path path = new Path();
+            AnimatorUpdateListener listener =
+                    newUpdateListener(startRect, endRect, endRadius, path);
+
+            ValueAnimator va =
+                    isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+            va.addListener(new AnimatorListenerAdapter() {
+                private ViewOutlineProvider mOldOutlineProvider;
+
+                public void onAnimationStart(Animator animation) {
+                    mOldOutlineProvider = target.getOutlineProvider();
+                    target.setOutlineProvider(null);
+
+                    target.setTranslationZ(-target.getElevation());
+                }
+
+                public void onAnimationEnd(Animator animation) {
+                    target.setTranslationZ(0);
+                    target.setClipPath(null);
+                    target.setOutlineProvider(mOldOutlineProvider);
+                }
+            });
+
+            va.addUpdateListener((anim) -> {
+                path.reset();
+                listener.onAnimationUpdate(anim);
+                target.setClipPath(path);
+            });
+
+            return va;
+        }
+    }
+
+    public static final class Circle extends SimpleRectShape {
+
+        @Override
+        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
+            canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p);
+        }
+
+        @Override
+        public void addShape(Path path, float offsetX, float offsetY, float radius) {
+            path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
+        }
+
+        @Override
+        protected float getStartRadius(Rect startRect) {
+            return startRect.width() / 2f;
+        }
+    }
+
+    public static class Square extends SimpleRectShape {
+
+        @Override
+        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,  Paint p) {
+            float cx = radius + offsetX;
+            float cy = radius + offsetY;
+            canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p);
+        }
+
+        @Override
+        public void addShape(Path path, float offsetX, float offsetY, float radius) {
+            float cx = radius + offsetX;
+            float cy = radius + offsetY;
+            path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW);
+        }
+
+        @Override
+        protected float getStartRadius(Rect startRect) {
+            return 0;
+        }
+    }
+
+    public static class RoundedSquare extends SimpleRectShape {
+
+        /**
+         * Ratio of corner radius to half size. Based on the
+         */
+        private final float mRadiusFactor;
+
+        public RoundedSquare(float radiusFactor) {
+            mRadiusFactor = radiusFactor;
+        }
+
+        @Override
+        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
+            float cx = radius + offsetX;
+            float cy = radius + offsetY;
+            float cr = radius * mRadiusFactor;
+            canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
+        }
+
+        @Override
+        public void addShape(Path path, float offsetX, float offsetY, float radius) {
+            float cx = radius + offsetX;
+            float cy = radius + offsetY;
+            float cr = radius * mRadiusFactor;
+            path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
+                    Path.Direction.CW);
+        }
+
+        @Override
+        protected float getStartRadius(Rect startRect) {
+            return (startRect.width() / 2f) * mRadiusFactor;
+        }
+    }
+
+    public static class TearDrop extends PathShape {
+
+        /**
+         * Radio of short radius to large radius, based on the shape options defined in the config.
+         */
+        private static final float RADIUS_RATIO = 15f / 50;
+
+        private final float[] mTempRadii = new float[8];
+
+        @Override
+        public void addShape(Path p, float offsetX, float offsetY, float r1) {
+            float r2 = r1 * RADIUS_RATIO;
+            float cx = r1 + offsetX;
+            float cy = r1 + offsetY;
+
+            p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
+                    Path.Direction.CW);
+        }
+
+        private float[] getRadiiArray(float r1, float r2) {
+            mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
+                    mTempRadii[6] = mTempRadii[7] = r1;
+            mTempRadii[4] = mTempRadii[5] = r2;
+            return mTempRadii;
+        }
+
+        @Override
+        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
+                float endRadius, Path outPath) {
+            float r1 = startRect.width() / 2f;
+            float r2 = r1 * RADIUS_RATIO;
+
+            float[] startValues = new float[] {
+                    startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
+            float[] endValues = new float[] {
+                    endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
+
+            FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
+
+            return (anim) -> {
+                float progress = (Float) anim.getAnimatedValue();
+                float[] values = evaluator.evaluate(progress, startValues, endValues);
+                outPath.addRoundRect(
+                        values[0], values[1], values[2], values[3],
+                        getRadiiArray(values[4], values[5]), Path.Direction.CW);
+            };
+        }
+    }
+
+    public static class Squircle extends PathShape {
+
+        /**
+         * Radio of radius to circle radius, based on the shape options defined in the config.
+         */
+        private static final float RADIUS_RATIO = 10f / 50;
+
+        @Override
+        public void addShape(Path p, float offsetX, float offsetY, float r) {
+            float cx = r + offsetX;
+            float cy = r + offsetY;
+            float control = r - r * RADIUS_RATIO;
+
+            p.moveTo(cx, cy - r);
+            addLeftCurve(cx, cy, r, control, p);
+            addRightCurve(cx, cy, r, control, p);
+            addLeftCurve(cx, cy, -r, -control, p);
+            addRightCurve(cx, cy, -r, -control, p);
+            p.close();
+        }
+
+        private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
+            path.cubicTo(
+                    cx - control, cy - r,
+                    cx - r, cy - control,
+                    cx - r, cy);
+        }
+
+        private void addRightCurve(float cx, float cy, float r, float control, Path path) {
+            path.cubicTo(
+                    cx - r, cy + control,
+                    cx - control, cy + r,
+                    cx, cy + r);
+        }
+
+        @Override
+        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
+                float endR, Path outPath) {
+
+            float startCX = startRect.exactCenterX();
+            float startCY = startRect.exactCenterY();
+            float startR = startRect.width() / 2f;
+            float startControl = startR - startR * RADIUS_RATIO;
+            float startHShift = 0;
+            float startVShift = 0;
+
+            float endCX = endRect.exactCenterX();
+            float endCY = endRect.exactCenterY();
+            // Approximate corner circle using bezier curves
+            // http://spencermortensen.com/articles/bezier-circle/
+            float endControl = endR * 0.551915024494f;
+            float endHShift = endRect.width() / 2f - endR;
+            float endVShift = endRect.height() / 2f - endR;
+
+            return (anim) -> {
+                float progress = (Float) anim.getAnimatedValue();
+
+                float cx = (1 - progress) * startCX + progress * endCX;
+                float cy = (1 - progress) * startCY + progress * endCY;
+                float r = (1 - progress) * startR + progress * endR;
+                float control = (1 - progress) * startControl + progress * endControl;
+                float hShift = (1 - progress) * startHShift + progress * endHShift;
+                float vShift = (1 - progress) * startVShift + progress * endVShift;
+
+                outPath.moveTo(cx, cy - vShift - r);
+                outPath.rLineTo(-hShift, 0);
+
+                addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
+                outPath.rLineTo(0, vShift + vShift);
+
+                addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
+                outPath.rLineTo(hShift + hShift, 0);
+
+                addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
+                outPath.rLineTo(0, -vShift - vShift);
+
+                addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
+                outPath.close();
+            };
+        }
+    }
+
+    /**
+     * Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable}
+     */
+    public static void init() {
+        if (!Utilities.ATLEAST_OREO) {
+            return;
+        }
+        new MainThreadExecutor().execute(FolderShape::pickShapeInBackground);
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    protected static void pickShapeInBackground() {
+        // Pick any large size
+        int size = 200;
+
+        Region full = new Region(0, 0, size, size);
+        Region iconR = new Region();
+        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
+                new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
+        drawable.setBounds(0, 0, size, size);
+        iconR.setPath(drawable.getIconMask(), full);
+
+        Path shapePath = new Path();
+        Region shapeR = new Region();
+        Rect tempRect = new Rect();
+
+        // Find the shape with minimum area of divergent region.
+        int minArea = Integer.MAX_VALUE;
+        FolderShape closestShape = null;
+        for (FolderShape shape : getAllShapes()) {
+            shapePath.reset();
+            shape.addShape(shapePath, 0, 0, size / 2f);
+            shapeR.setPath(shapePath, full);
+            shapeR.op(iconR, Op.XOR);
+
+            RegionIterator itr = new RegionIterator(shapeR);
+            int area = 0;
+
+            while (itr.next(tempRect)) {
+                area += tempRect.width() * tempRect.height();
+            }
+            if (area < minArea) {
+                minArea = area;
+                closestShape = shape;
+            }
+        }
+
+        if (closestShape != null) {
+            FolderShape shape = closestShape;
+            new MainThreadExecutor().execute(() -> updateFolderShape(shape));
+        }
+    }
+
+    private static void updateFolderShape(FolderShape shape) {
+        sInstance = shape;
+        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+        if (app == null) {
+            return;
+        }
+        Launcher launcher = (Launcher) app.getModel().getCallback();
+        if (launcher != null) {
+            launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> {
+                if (v instanceof FolderIcon) {
+                    v.invalidate();
+                }
+                return false;
+            });
+        }
+    }
+}
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ceb1a8c..8443953 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.folder.FolderShape.getShape;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -49,16 +51,6 @@
 
     private static final int CONSUMPTION_ANIMATION_DURATION = 100;
 
-    private final PorterDuffXfermode mClipPorterDuffXfermode
-            = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
-    // Create a RadialGradient such that it draws a black circle and then extends with
-    // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
-    // just at the edge quickly change it to transparent.
-    private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
-            new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
-            new float[] {0, 0.999f, 1},
-            Shader.TileMode.CLAMP);
-
     private final PorterDuffXfermode mShadowPorterDuffXfermode
             = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
     private RadialGradient mShadowShader = null;
@@ -208,8 +200,7 @@
         mPaint.setStyle(Paint.Style.FILL);
         mPaint.setColor(getBgColor());
 
-        drawCircle(canvas, 0 /* deltaRadius */);
-
+        getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
         drawShadow(canvas);
     }
 
@@ -244,7 +235,7 @@
         mPaint.setShader(null);
         if (canvas.isHardwareAccelerated()) {
             mPaint.setXfermode(mShadowPorterDuffXfermode);
-            canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+            getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint);
             mPaint.setXfermode(null);
         }
 
@@ -287,7 +278,10 @@
         mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
         mPaint.setStyle(Paint.Style.STROKE);
         mPaint.setStrokeWidth(mStrokeWidth);
-        drawCircle(canvas, 1 /* deltaRadius */);
+
+        float inset = 1f;
+        getShape().drawShape(canvas,
+                getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
     }
 
     public void drawLeaveBehind(Canvas canvas) {
@@ -296,40 +290,17 @@
 
         mPaint.setStyle(Paint.Style.FILL);
         mPaint.setColor(Color.argb(160, 245, 245, 245));
-        drawCircle(canvas, 0 /* deltaRadius */);
+        getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
 
         mScale = originalScale;
     }
 
-    private void drawCircle(Canvas canvas,float deltaRadius) {
-        float radius = getScaledRadius();
-        canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
-                radius - deltaRadius, mPaint);
-    }
-
     public Path getClipPath() {
         mPath.reset();
-        float r = getScaledRadius();
-        mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+        getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
         return mPath;
     }
 
-    // It is the callers responsibility to save and restore the canvas layers.
-    void clipCanvasHardware(Canvas canvas) {
-        mPaint.setColor(Color.BLACK);
-        mPaint.setStyle(Paint.Style.FILL);
-        mPaint.setXfermode(mClipPorterDuffXfermode);
-
-        float radius = getScaledRadius();
-        mShaderMatrix.setScale(radius, radius);
-        mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
-        mClipShader.setLocalMatrix(mShaderMatrix);
-        mPaint.setShader(mClipShader);
-        canvas.drawPaint(mPaint);
-        mPaint.setXfermode(null);
-        mPaint.setShader(null);
-    }
-
     private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
         if (mDrawingDelegate != delegate) {
             delegate.addFolderBackground(this);
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
index cadc6e3..b636c6d 100644
--- a/src/com/android/launcher3/graphics/IconShapeOverride.java
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -26,9 +26,6 @@
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.SystemClock;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -42,6 +39,9 @@
 import java.lang.reflect.Field;
 
 import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
 
 /**
  * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java
index 5b74d1d..2afe713 100644
--- a/src/com/android/launcher3/icons/BaseIconCache.java
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -275,7 +275,7 @@
     protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
         info.title = Utilities.trim(entry.title);
         info.contentDescription = entry.contentDescription;
-        ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
+        info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
     }
 
     public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
diff --git a/src/com/android/launcher3/icons/BaseIconFactory.java b/src/com/android/launcher3/icons/BaseIconFactory.java
index db723b7..cd60de5 100644
--- a/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -69,14 +69,14 @@
 
     public ShadowGenerator getShadowGenerator() {
         if (mShadowGenerator == null) {
-            mShadowGenerator = new ShadowGenerator(mContext);
+            mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
         }
         return mShadowGenerator;
     }
 
     public IconNormalizer getNormalizer() {
         if (mNormalizer == null) {
-            mNormalizer = new IconNormalizer(mContext);
+            mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
         }
         return mNormalizer;
     }
@@ -192,18 +192,18 @@
             }
             AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
             dr.setBounds(0, 0, 1, 1);
-            scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
-            if (ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
+            scale = getNormalizer().getScale(icon, outIconBounds);
+            if (ATLEAST_OREO && !(icon instanceof AdaptiveIconDrawable)) {
                 FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
                 fsd.setDrawable(icon);
                 fsd.setScale(scale);
                 icon = dr;
-                scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+                scale = getNormalizer().getScale(icon, outIconBounds);
 
                 ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
             }
         } else {
-            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+            scale = getNormalizer().getScale(icon, outIconBounds);
         }
 
         outScale[0] = scale;
diff --git a/src/com/android/launcher3/icons/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java
index ebe0511..245561e 100644
--- a/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/src/com/android/launcher3/icons/BitmapInfo.java
@@ -18,8 +18,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 
-import com.android.launcher3.ItemInfoWithIcon;
-
 public class BitmapInfo {
 
     public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
@@ -27,11 +25,6 @@
     public Bitmap icon;
     public int color;
 
-    public void applyTo(ItemInfoWithIcon info) {
-        info.iconBitmap = icon;
-        info.iconColor = color;
-    }
-
     public void applyTo(BitmapInfo info) {
         info.icon = icon;
         info.color = color;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 4349455..41a53e5 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -139,7 +139,7 @@
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
         if (info.getTargetComponent() == null) {
-            getDefaultIcon(info.user).applyTo(info);
+            info.applyFrom(getDefaultIcon(info.user));
             info.title = "";
             info.contentDescription = "";
         } else {
diff --git a/src/com/android/launcher3/icons/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java
index 7317782..8eb8252 100644
--- a/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/src/com/android/launcher3/icons/IconNormalizer.java
@@ -20,21 +20,13 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.Path;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 
 import java.nio.ByteBuffer;
 
@@ -58,9 +50,6 @@
 
     private static final int MIN_VISIBLE_ALPHA = 40;
 
-    // Shape detection related constants
-    private static final float BOUND_RATIO_MARGIN = .05f;
-    private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
     private static final float SCALE_NOT_INITIALIZED = 0;
 
     // Ratio of the diameter of an normalized circular icon to the actual icon size.
@@ -69,8 +58,6 @@
     private final int mMaxSize;
     private final Bitmap mBitmap;
     private final Canvas mCanvas;
-    private final Paint mPaintMaskShape;
-    private final Paint mPaintMaskShapeOutline;
     private final byte[] mPixels;
 
     private final Rect mAdaptiveIconBounds;
@@ -80,13 +67,11 @@
     private final float[] mLeftBorder;
     private final float[] mRightBorder;
     private final Rect mBounds;
-    private final Path mShapePath;
-    private final Matrix mMatrix;
 
     /** package private **/
-    IconNormalizer(Context context) {
+    IconNormalizer(Context context, int iconBitmapSize) {
         // Use twice the icon size as maximum size to avoid scaling down twice.
-        mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
+        mMaxSize = iconBitmapSize * 2;
         mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
         mCanvas = new Canvas(mBitmap);
         mPixels = new byte[mMaxSize * mMaxSize];
@@ -95,89 +80,10 @@
         mBounds = new Rect();
         mAdaptiveIconBounds = new Rect();
 
-        mPaintMaskShape = new Paint();
-        mPaintMaskShape.setColor(Color.RED);
-        mPaintMaskShape.setStyle(Paint.Style.FILL);
-        mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
-
-        mPaintMaskShapeOutline = new Paint();
-        mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
-        mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
-        mPaintMaskShapeOutline.setColor(Color.BLACK);
-        mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
-        mShapePath = new Path();
-        mMatrix = new Matrix();
         mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
     }
 
     /**
-     * Returns if the shape of the icon is same as the path.
-     * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
-     */
-    private boolean isShape(Path maskPath) {
-        // Condition1:
-        // If width and height of the path not close to a square, then the icon shape is
-        // not same as the mask shape.
-        float iconRatio = ((float) mBounds.width()) / mBounds.height();
-        if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
-            if (DEBUG) {
-                Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
-            }
-            return false;
-        }
-
-        // Condition 2:
-        // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
-        // should generate transparent image, if the actual icon is equivalent to the shape.
-
-        // Fit the shape within the icon's bounding box
-        mMatrix.reset();
-        mMatrix.setScale(mBounds.width(), mBounds.height());
-        mMatrix.postTranslate(mBounds.left, mBounds.top);
-        maskPath.transform(mMatrix, mShapePath);
-
-        // XOR operation
-        mCanvas.drawPath(mShapePath, mPaintMaskShape);
-
-        // DST_OUT operation around the mask path outline
-        mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
-
-        // Check if the result is almost transparent
-        return isTransparentBitmap();
-    }
-
-    /**
-     * Used to determine if certain the bitmap is transparent.
-     */
-    private boolean isTransparentBitmap() {
-        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
-        buffer.rewind();
-        mBitmap.copyPixelsToBuffer(buffer);
-
-        int y = mBounds.top;
-        // buffer position
-        int index = y * mMaxSize;
-        // buffer shift after every row, width of buffer = mMaxSize
-        int rowSizeDiff = mMaxSize - mBounds.right;
-
-        int sum = 0;
-        for (; y < mBounds.bottom; y++) {
-            index += mBounds.left;
-            for (int x = mBounds.left; x < mBounds.right; x++) {
-                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
-                    sum++;
-                }
-                index++;
-            }
-            index += rowSizeDiff;
-        }
-
-        float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
-        return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
-    }
-
-    /**
      * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
      * matches the design guidelines for a launcher icon.
      *
@@ -191,19 +97,14 @@
      *
      * @param outBounds optional rect to receive the fraction distance from each edge.
      */
-    public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
-            @Nullable Path path, @Nullable boolean[] outMaskShape) {
-        if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+    public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
+        if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
             if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
                 if (outBounds != null) {
                     outBounds.set(mAdaptiveIconBounds);
                 }
                 return mAdaptiveIconScale;
             }
-            if (d instanceof FolderAdaptiveIcon) {
-                // Since we just want the scale, avoid heavy drawing operations
-                d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
-            }
         }
         int width = d.getIntrinsicWidth();
         int height = d.getIntrinsicHeight();
@@ -307,14 +208,10 @@
                     1 - ((float) mBounds.right) / width,
                     1 - ((float) mBounds.bottom) / height);
         }
-
-        if (outMaskShape != null && outMaskShape.length > 0) {
-            outMaskShape[0] = isShape(path);
-        }
         float areaScale = area / (width * height);
         // Use sqrt of the final ratio as the images is scaled across both width and height.
         float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
-        if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
+        if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
                 mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
             mAdaptiveIconScale = scale;
             mAdaptiveIconBounds.set(mBounds);
diff --git a/src/com/android/launcher3/icons/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java
index 57d463a..6491b7e 100644
--- a/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.icons;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BlurMaskFilter;
@@ -28,23 +27,19 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.RectF;
 
-import com.android.launcher3.LauncherAppState;
-
 import androidx.core.graphics.ColorUtils;
 
 /**
  * Utility class to add shadows to bitmaps.
  */
 public class ShadowGenerator {
-
-    // Percent of actual icon size
-    private static final float HALF_DISTANCE = 0.5f;
     public static final float BLUR_FACTOR = 0.5f/48;
 
     // Percent of actual icon size
     public static final float KEY_SHADOW_DISTANCE = 1f/48;
     private static final int KEY_SHADOW_ALPHA = 61;
-
+    // Percent of actual icon size
+    private static final float HALF_DISTANCE = 0.5f;
     private static final int AMBIENT_SHADOW_ALPHA = 30;
 
     private final int mIconSize;
@@ -53,8 +48,8 @@
     private final Paint mDrawPaint;
     private final BlurMaskFilter mDefaultBlurMaskFilter;
 
-    public ShadowGenerator(Context context) {
-        mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
+    public ShadowGenerator(int iconSize) {
+        mIconSize = iconSize;
         mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index fcdc088..c9d8e3e 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -27,10 +27,10 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -107,13 +107,9 @@
     }
 
     public void bindDeepShortcuts(BgDataModel dataModel) {
-        final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
-        scheduleCallbackTask(new CallbackTask() {
-            @Override
-            public void execute(Callbacks callbacks) {
-                callbacks.bindDeepShortcutMap(shortcutMapCopy);
-            }
-        });
+        final HashMap<ComponentKey, Integer> shortcutMapCopy =
+                new HashMap<>(dataModel.deepShortcutMap);
+        scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
     }
 
     public void bindUpdatedWidgets(BgDataModel dataModel) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 81eefc4..151d6f4 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.MultiHashMap;
 import com.google.protobuf.nano.MessageNano;
 
 import java.io.FileDescriptor;
@@ -97,9 +96,9 @@
     public boolean hasShortcutHostPermission;
 
     /**
-     * Maps all launcher activities to the id's of their shortcuts (if they have any).
+     * Maps all launcher activities to counts of their shortcuts.
      */
-    public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
+    public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>();
 
     /**
      * Entire list of widgets.
@@ -154,14 +153,11 @@
         }
 
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
-            writer.println(prefix + "shortcuts");
-            for (ArrayList<String> map : deepShortcutMap.values()) {
-                writer.print(prefix + "  ");
-                for (String str : map) {
-                    writer.print(str + ", ");
-                }
-                writer.println();
+            writer.println(prefix + "shortcut counts ");
+            for (Integer count : deepShortcutMap.values()) {
+                writer.print(count + ", ");
             }
+            writer.println();
         }
     }
 
@@ -359,9 +355,9 @@
     }
 
     /**
-     * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
+     * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
      */
-    public synchronized void updateDeepShortcutMap(
+    public synchronized void updateDeepShortcutCounts(
             String packageName, UserHandle user, List<ShortcutInfoCompat> shortcuts) {
         if (packageName != null) {
             Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
@@ -381,7 +377,9 @@
             if (shouldShowInContainer) {
                 ComponentKey targetComponent
                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
-                deepShortcutMap.addToList(targetComponent, shortcut.getId());
+
+                Integer previousCount = deepShortcutMap.get(targetComponent);
+                deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
             }
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 94cf5c2..ea4d32b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -153,7 +153,7 @@
         info.title = getTitle();
         // the fallback icon
         if (!loadIcon(info)) {
-            mIconCache.getDefaultIcon(info.user).applyTo(info);
+            info.applyFrom(mIconCache.getDefaultIcon(info.user));
         }
 
         // TODO: If there's an explicit component and we can't install that, delete it.
@@ -176,7 +176,7 @@
                 BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
                 li.recycle();
                 if (iconInfo != null) {
-                    iconInfo.applyTo(info);
+                    info.applyFrom(iconInfo);
                     return true;
                 }
             }
@@ -185,7 +185,7 @@
         // Failed to load from resource, try loading from DB.
         byte[] data = getBlob(iconIndex);
         try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-            li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
+            info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Failed to load icon for info " + info, e);
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 2c15df1..1d18e76 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -34,7 +34,6 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
@@ -42,9 +41,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -333,20 +331,16 @@
     }
 
     public void bindDeepShortcuts() {
-        final MultiHashMap<ComponentKey, String> shortcutMapCopy;
+        final HashMap<ComponentKey, Integer> shortcutMapCopy;
         synchronized (mBgDataModel) {
-            shortcutMapCopy = mBgDataModel.deepShortcutMap.clone();
+            shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
         }
-        Runnable r = new Runnable() {
-            @Override
-            public void run() {
-                Callbacks callbacks = mCallbacks.get();
-                if (callbacks != null) {
-                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
-                }
+        mUiExecutor.execute(() -> {
+            Callbacks callbacks = mCallbacks.get();
+            if (callbacks != null) {
+                callbacks.bindDeepShortcutMap(shortcutMapCopy);
             }
-        };
-        mUiExecutor.execute(r);
+        });
     }
 
     public void bindAllApps() {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1ec7af2..8b3e2c9 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -498,8 +498,8 @@
                                             c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null;
 
                                     LauncherIcons li = LauncherIcons.obtain(context);
-                                    li.createShortcutIcon(pinnedShortcut,
-                                            true /* badged */, fallbackIconProvider).applyTo(info);
+                                    info.applyFrom(li.createShortcutIcon(pinnedShortcut,
+                                            true /* badged */, fallbackIconProvider));
                                     li.recycle();
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
@@ -861,7 +861,7 @@
                 if (mUserManager.isUserUnlocked(user)) {
                     List<ShortcutInfoCompat> shortcuts =
                             mShortcutManager.queryForAllShortcuts(user);
-                    mBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
+                    mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
                 }
             }
         }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 201a63e..0f67f0c 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -194,7 +194,7 @@
                             BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
                             li.recycle();
                             if (iconInfo != null) {
-                                iconInfo.applyTo(si);
+                                si.applyFrom(iconInfo);
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 020bc41..e99fed9 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -96,8 +96,8 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
-                            .applyTo(shortcutInfo);
+                    shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
+                            Provider.of(shortcutInfo.iconBitmap)));
                     li.recycle();
                     updatedShortcutInfos.add(shortcutInfo);
                 }
@@ -116,7 +116,7 @@
 
         if (mUpdateIdMap) {
             // Update the deep shortcut map if the list of ids has changed for an activity.
-            dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+            dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
             bindDeepShortcuts(dataModel);
         }
     }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 9f02d4f..8e7557a 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -95,7 +95,7 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
+                    si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)));
                     li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
@@ -117,7 +117,7 @@
         }
 
         if (isUserUnlocked) {
-            dataModel.updateDeepShortcutMap(
+            dataModel.updateDeepShortcutCounts(
                     null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
         }
         bindDeepShortcuts(dataModel);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b9e6a98..4f1fcda 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -216,13 +216,13 @@
             BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
         PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
         populateAndShow(icon,
-                popupDataProvider.getShortcutIdsForItem(item),
+                popupDataProvider.getShortcutCountForItem(item),
                 popupDataProvider.getNotificationKeysForItem(item),
                 factory.getEnabledShortcuts(mLauncher, item));
     }
 
     @TargetApi(Build.VERSION_CODES.P)
-    protected void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
+    protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
             final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
         mNumNotifications = notificationKeys.size();
         mOriginalIcon = originalIcon;
@@ -240,12 +240,12 @@
         int viewsToFlip = getChildCount();
         mSystemShortcutContainer = this;
 
-        if (!shortcutIds.isEmpty()) {
+        if (shortcutCount > 0) {
             if (mNotificationItemView != null) {
                 mNotificationItemView.addGutter();
             }
 
-            for (int i = shortcutIds.size(); i > 0; i--) {
+            for (int i = shortcutCount; i > 0; i--) {
                 mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this));
             }
             updateHiddenShortcuts();
@@ -284,7 +284,7 @@
         final Looper workerLooper = LauncherModel.getWorkerLooper();
         new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
-                this, shortcutIds, mShortcuts, notificationKeys));
+                this, mShortcuts, notificationKeys));
     }
 
     private String getTitleForAccessibility() {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 4d5a9c6..3206503 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
@@ -52,8 +51,8 @@
 
     private final Launcher mLauncher;
 
-    /** Maps launcher activity components to their list of shortcut ids. */
-    private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+    /** Maps launcher activity components to a count of how many shortcuts they have. */
+    private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
     /** Maps packages to their BadgeInfo's . */
     private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
     /** Maps packages to their Widgets */
@@ -146,22 +145,22 @@
         }
     }
 
-    public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+    public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
         mDeepShortcutMap = deepShortcutMapCopy;
         if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
     }
 
-    public List<String> getShortcutIdsForItem(ItemInfo info) {
+    public int getShortcutCountForItem(ItemInfo info) {
         if (!DeepShortcutManager.supportsShortcuts(info)) {
-            return Collections.EMPTY_LIST;
+            return 0;
         }
         ComponentName component = info.getTargetComponent();
         if (component == null) {
-            return Collections.EMPTY_LIST;
+            return 0;
         }
 
-        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
-        return ids == null ? Collections.EMPTY_LIST : ids;
+        Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+        return count == null ? 0 : count;
     }
 
     public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index c14c00e..2c59202 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -124,7 +124,7 @@
 
     public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
             final Handler uiHandler, final PopupContainerWithArrow container,
-            final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
+            final List<DeepShortcutView> shortcutViews,
             final List<NotificationKeyData> notificationKeys) {
         final ComponentName activity = originalInfo.getTargetComponent();
         final UserHandle user = originalInfo.user;
@@ -141,7 +141,7 @@
             }
 
             List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
-                    .queryForShortcutsContainer(activity, shortcutIds, user);
+                    .queryForShortcutsContainer(activity, user);
             String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
                     : notificationKeys.get(0).shortcutId;
             shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
@@ -150,7 +150,7 @@
                 final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
                 // Use unbadged icon for the menu.
                 LauncherIcons li = LauncherIcons.obtain(launcher);
-                li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
+                si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */));
                 li.recycle();
                 si.rank = i;
 
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
new file mode 100644
index 0000000..c76fb96
--- /dev/null
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+public class RemoteActionShortcut extends SystemShortcut<Launcher> {
+    private static final String TAG = "RemoteActionShortcut";
+
+    private final RemoteAction mAction;
+
+    public RemoteActionShortcut(RemoteAction action) {
+        super(action.getIcon(), action.getTitle(), action.getContentDescription(),
+                R.id.action_remote_action_shortcut);
+        mAction = action;
+    }
+
+    @Override
+    public View.OnClickListener getOnClickListener(
+            final Launcher launcher, final ItemInfo itemInfo) {
+        return view -> {
+            AbstractFloatingView.closeAllOpenViews(launcher);
+
+            try {
+                mAction.getActionIntent().send(
+                        launcher,
+                        0,
+                        new Intent().putExtra(
+                                Intent.EXTRA_PACKAGE_NAME,
+                                itemInfo.getTargetComponent().getPackageName()),
+                        (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
+                            if (resultData != null && !resultData.isEmpty()) {
+                                Log.e(TAG, "Remote action returned result: " + mAction.getTitle()
+                                        + " : " + resultData);
+                                Toast.makeText(launcher, resultData, Toast.LENGTH_SHORT).show();
+                            }
+                        },
+                        new Handler(Looper.getMainLooper()));
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Remote action canceled: " + mAction.getTitle(), e);
+                Toast.makeText(launcher, launcher.getString(
+                        R.string.remote_action_failed,
+                        mAction.getTitle()),
+                        Toast.LENGTH_SHORT)
+                        .show();
+            }
+
+            launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+                    LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
+        };
+    }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index b80ba8a..f9a2007 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -6,8 +6,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
@@ -36,7 +38,7 @@
 public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
     private final int mIconResId;
     private final int mLabelResId;
-    private final Drawable mIcon;
+    private final Icon mIcon;
     private final CharSequence mLabel;
     private final CharSequence mContentDescription;
     private final int mAccessibilityActionId;
@@ -50,7 +52,7 @@
         mContentDescription = null;
     }
 
-    public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription,
+    public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
             int accessibilityActionId) {
         mIcon = icon;
         mLabel = label;
@@ -71,7 +73,9 @@
 
     public void setIconAndLabelFor(View iconView, TextView labelView) {
         if (mIcon != null) {
-            iconView.setBackground(mIcon);
+            mIcon.loadDrawableAsync(iconView.getContext(),
+                    iconView::setBackground,
+                    new Handler(Looper.getMainLooper()));
         } else {
             iconView.setBackgroundResource(mIconResId);
         }
@@ -85,7 +89,9 @@
 
     public void setIconAndContentDescriptionFor(ImageView view) {
         if (mIcon != null) {
-            view.setImageDrawable(mIcon);
+            mIcon.loadDrawableAsync(view.getContext(),
+                    view::setImageDrawable,
+                    new Handler(Looper.getMainLooper()));
         } else {
             view.setImageResource(mIconResId);
         }
diff --git a/src/com/android/launcher3/settings/IconBadgingPreference.java b/src/com/android/launcher3/settings/IconBadgingPreference.java
new file mode 100644
index 0000000..7c97b38
--- /dev/null
+++ b/src/com/android/launcher3/settings/IconBadgingPreference.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.settings;
+
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGS;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A {@link Preference} for indicating icon badging status.
+ * Also has utility methods for updating UI based on badging status changes.
+ */
+public class IconBadgingPreference extends Preference
+        implements SecureSettingsObserver.OnChangeListener {
+
+    private boolean mWidgetFrameVisible = false;
+
+    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+    private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+    public IconBadgingPreference(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public IconBadgingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public IconBadgingPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public IconBadgingPreference(Context context) {
+        super(context);
+    }
+
+    private void setWidgetFrameVisible(boolean isVisible) {
+        if (mWidgetFrameVisible != isVisible) {
+            mWidgetFrameVisible = isVisible;
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+        if (widgetFrame != null) {
+            widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onSettingsChanged(boolean enabled) {
+        int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
+
+        boolean serviceEnabled = true;
+        if (enabled) {
+            // Check if the listener is enabled or not.
+            String enabledListeners = Settings.Secure.getString(
+                    getContext().getContentResolver(), NOTIFICATION_ENABLED_LISTENERS);
+            ComponentName myListener =
+                    new ComponentName(getContext(), NotificationListener.class);
+            serviceEnabled = enabledListeners != null &&
+                    (enabledListeners.contains(myListener.flattenToString()) ||
+                            enabledListeners.contains(myListener.flattenToShortString()));
+            if (!serviceEnabled) {
+                summary = R.string.title_missing_notification_access;
+            }
+        }
+        setWidgetFrameVisible(!serviceEnabled);
+        setFragment(serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
+        setSummary(summary);
+    }
+
+    public static class NotificationAccessConfirmation
+            extends DialogFragment implements DialogInterface.OnClickListener {
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+            String msg = context.getString(R.string.msg_missing_notification_access,
+                    context.getString(R.string.derived_app_name));
+            return new AlertDialog.Builder(context)
+                    .setTitle(R.string.title_missing_notification_access)
+                    .setMessage(msg)
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .setPositiveButton(R.string.title_change_settings, this)
+                    .create();
+        }
+
+        @Override
+        public void onClick(DialogInterface dialogInterface, int i) {
+            ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
+            Bundle showFragmentArgs = new Bundle();
+            showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
+
+            Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
+                    .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
+            getActivity().startActivity(intent);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/settings/PreferenceHighlighter.java b/src/com/android/launcher3/settings/PreferenceHighlighter.java
new file mode 100644
index 0000000..4ed4cf1
--- /dev/null
+++ b/src/com/android/launcher3/settings/PreferenceHighlighter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.settings;
+
+import static androidx.core.graphics.ColorUtils.setAlphaComponent;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.util.Themes;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Utility class for highlighting a preference
+ */
+public class PreferenceHighlighter extends ItemDecoration implements Runnable {
+
+    private static final Property<PreferenceHighlighter, Integer> HIGHLIGHT_COLOR =
+            new Property<PreferenceHighlighter, Integer>(Integer.TYPE, "highlightColor") {
+
+                @Override
+                public Integer get(PreferenceHighlighter highlighter) {
+                    return highlighter.mHighlightColor;
+                }
+
+                @Override
+                public void set(PreferenceHighlighter highlighter, Integer value) {
+                    highlighter.mHighlightColor = value;
+                    highlighter.mRv.invalidateItemDecorations();
+                }
+            };
+
+    private static final long HIGHLIGHT_DURATION = 15000L;
+    private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
+    private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;
+    private static final int END_COLOR = setAlphaComponent(Color.WHITE, 0);
+
+    private final Paint mPaint = new Paint();
+    private final RecyclerView mRv;
+    private final int mIndex;
+
+    private boolean mHighLightStarted = false;
+    private int mHighlightColor = END_COLOR;
+
+
+    public PreferenceHighlighter(RecyclerView rv, int index) {
+        mRv = rv;
+        mIndex = index;
+    }
+
+    @Override
+    public void run() {
+        mRv.addItemDecoration(this);
+        mRv.smoothScrollToPosition(mIndex);
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, State state) {
+        ViewHolder holder = parent.findViewHolderForAdapterPosition(mIndex);
+        if (holder == null) {
+            return;
+        }
+        if (!mHighLightStarted && state.getRemainingScrollVertical() != 0) {
+            // Wait until scrolling stopped
+            return;
+        }
+
+        if (!mHighLightStarted) {
+            // Start highlight
+            int colorTo = setAlphaComponent(Themes.getColorAccent(mRv.getContext()), 66);
+            ObjectAnimator anim = ObjectAnimator.ofArgb(this, HIGHLIGHT_COLOR, END_COLOR, colorTo);
+            anim.setDuration(HIGHLIGHT_FADE_IN_DURATION);
+            anim.setRepeatMode(ValueAnimator.REVERSE);
+            anim.setRepeatCount(4);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    removeHighlight();
+                }
+            });
+            anim.start();
+            mHighLightStarted = true;
+        }
+
+        View view = holder.itemView;
+        mPaint.setColor(mHighlightColor);
+        c.drawRect(0, view.getY(), parent.getWidth(), view.getY() + view.getHeight(), mPaint);
+    }
+
+    private void removeHighlight() {
+        ObjectAnimator anim = ObjectAnimator.ofArgb(
+                this, HIGHLIGHT_COLOR, mHighlightColor, END_COLOR);
+        anim.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
+        anim.setStartDelay(HIGHLIGHT_DURATION);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mRv.removeItemDecoration(PreferenceHighlighter.this);
+            }
+        });
+        anim.start();
+    }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
new file mode 100644
index 0000000..4c022b4
--- /dev/null
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.settings;
+
+import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceGroup.PreferencePositionCallback;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Settings activity for Launcher. Currently implements the following setting: Allow rotation
+ */
+public class SettingsActivity extends Activity
+        implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+    private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
+
+    private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
+    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+    private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+    public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
+    private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
+    public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            Bundle args = new Bundle();
+            String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
+            if (!TextUtils.isEmpty(prefKey)) {
+                args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
+            }
+
+            Fragment f = Fragment.instantiate(
+                    this, getString(R.string.settings_fragment_name), args);
+            // Display the fragment as the main content.
+            getFragmentManager().beginTransaction()
+                    .replace(android.R.id.content, f)
+                    .commit();
+        }
+    }
+
+    private boolean startFragment(String fragment, Bundle args, String key) {
+        if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
+            // Sometimes onClick can come after onPause because of being posted on the handler.
+            // Skip starting new fragments in that case.
+            return false;
+        }
+        Fragment f = Fragment.instantiate(this, fragment, args);
+        if (f instanceof DialogFragment) {
+            ((DialogFragment) f).show(getFragmentManager(), key);
+        } else {
+            getFragmentManager()
+                    .beginTransaction()
+                    .replace(android.R.id.content, f)
+                    .addToBackStack(key)
+                    .commit();
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onPreferenceStartFragment(
+            PreferenceFragment preferenceFragment, Preference pref) {
+        return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
+    }
+
+    @Override
+    public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+        Bundle args = new Bundle();
+        args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+        return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
+    }
+
+    /**
+     * This fragment shows the launcher preferences.
+     */
+    public static class LauncherSettingsFragment extends PreferenceFragment {
+
+        private SecureSettingsObserver mIconBadgingObserver;
+
+        private String mHighLightKey;
+        private boolean mPreferenceHighlighted = false;
+
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            final Bundle args = getArguments();
+            mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY);
+            if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) {
+                rootKey = getParentKeyForPref(mHighLightKey);
+            }
+
+            if (savedInstanceState != null) {
+                mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
+            }
+
+            getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
+            setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
+
+            PreferenceScreen screen = getPreferenceScreen();
+            for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
+                Preference preference = screen.getPreference(i);
+                if (!initPreference(preference)) {
+                    screen.removePreference(preference);
+                }
+            }
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
+        }
+
+        protected String getParentKeyForPref(String key) {
+            return null;
+        }
+
+        /**
+         * Initializes a preference. This is called for every preference. Returning false here
+         * will remove that preference from the list.
+         */
+        protected boolean initPreference(Preference preference) {
+            switch (preference.getKey()) {
+                case ICON_BADGING_PREFERENCE_KEY:
+                    if (!Utilities.ATLEAST_OREO ||
+                            !getResources().getBoolean(R.bool.notification_badging_enabled)) {
+                        return false;
+                    }
+
+                    // Listen to system notification badge settings while this UI is active.
+                    mIconBadgingObserver = newNotificationSettingsObserver(
+                            getActivity(), (IconBadgingPreference) preference);
+                    mIconBadgingObserver.register();
+                    // Also listen if notification permission changes
+                    mIconBadgingObserver.getResolver().registerContentObserver(
+                            Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
+                            mIconBadgingObserver);
+                    mIconBadgingObserver.dispatchOnChange();
+                    return true;
+
+                case ADD_ICON_PREFERENCE_KEY:
+                    return Utilities.ATLEAST_OREO;
+
+                case IconShapeOverride.KEY_PREFERENCE:
+                    if (!IconShapeOverride.isSupported(getActivity())) {
+                        return false;
+                    }
+                    IconShapeOverride.handlePreferenceUi((ListPreference) preference);
+                    return true;
+
+                case ALLOW_ROTATION_PREFERENCE_KEY:
+                    if (getResources().getBoolean(R.bool.allow_rotation)) {
+                        // Launcher supports rotation by default. No need to show this setting.
+                        return false;
+                    }
+                    // Initialize the UI once
+                    preference.setDefaultValue(getAllowRotationDefaultValue());
+                    return true;
+
+                case FLAGS_PREFERENCE_KEY:
+                    // Only show flag toggler UI if this build variant implements that.
+                    return FeatureFlags.showFlagTogglerUi(getContext());
+            }
+
+            return true;
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+
+            if (isAdded() && !mPreferenceHighlighted) {
+                PreferenceHighlighter highlighter = createHighlighter();
+                if (highlighter != null) {
+                    getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
+                    mPreferenceHighlighted = true;
+                }
+            }
+        }
+
+        private PreferenceHighlighter createHighlighter() {
+            if (TextUtils.isEmpty(mHighLightKey)) {
+                return null;
+            }
+
+            PreferenceScreen screen = getPreferenceScreen();
+            if (screen == null) {
+                return null;
+            }
+
+            RecyclerView list = getListView();
+            PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
+            int position = callback.getPreferenceAdapterPosition(mHighLightKey);
+            return position >= 0 ? new PreferenceHighlighter(list, position) : null;
+        }
+
+        @Override
+        public void onDestroy() {
+            if (mIconBadgingObserver != null) {
+                mIconBadgingObserver.unregister();
+                mIconBadgingObserver = null;
+            }
+            super.onDestroy();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 24e2e2f..e70aac6 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -94,10 +94,11 @@
      * Gets all the manifest and dynamic shortcuts associated with the given package and user,
      * to be displayed in the shortcuts container on long press.
      */
+    @TargetApi(25)
     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
-            List<String> ids, UserHandle user) {
+            UserHandle user) {
         return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
-                activity.getPackageName(), activity, ids, user);
+                activity.getPackageName(), activity, null, user);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java
deleted file mode 100644
index c9fe228..0000000
--- a/src/com/android/launcher3/util/ListViewHighlighter.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.animation.ArgbEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AbsListView.RecyclerListener;
-import android.widget.ListView;
-
-import com.android.launcher3.R;
-
-import androidx.core.graphics.ColorUtils;
-
-/**
- * Utility class to scroll and highlight a list view item
- */
-public class ListViewHighlighter implements OnScrollListener, RecyclerListener,
-        OnLayoutChangeListener {
-
-    private final ListView mListView;
-    private int mPosHighlight;
-
-    private boolean mColorAnimated = false;
-
-    public ListViewHighlighter(ListView listView, int posHighlight) {
-        mListView = listView;
-        mPosHighlight = posHighlight;
-        mListView.setOnScrollListener(this);
-        mListView.setRecyclerListener(this);
-        mListView.addOnLayoutChangeListener(this);
-
-        mListView.post(this::tryHighlight);
-    }
-
-    @Override
-    public void onLayoutChange(View v, int left, int top, int right, int bottom,
-            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-        mListView.post(this::tryHighlight);
-    }
-
-    private void tryHighlight() {
-        if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
-            return;
-        }
-        if (!highlightIfVisible(mListView.getFirstVisiblePosition(),
-                mListView.getLastVisiblePosition())) {
-            mListView.smoothScrollToPosition(mPosHighlight);
-        }
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView absListView, int i) { }
-
-    @Override
-    public void onScroll(AbsListView view, int firstVisibleItem,
-            int visibleItemCount, int totalItemCount) {
-        highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount - 1);
-    }
-
-    private boolean highlightIfVisible(int start, int end) {
-        if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
-            return false;
-        }
-        if (start > mPosHighlight || mPosHighlight > end) {
-            return false;
-        }
-        highlightView(mListView.getChildAt(mPosHighlight - start));
-
-        // finish highlight
-        mListView.setOnScrollListener(null);
-        mListView.removeOnLayoutChangeListener(this);
-
-        mPosHighlight = -1;
-        return true;
-    }
-
-    @Override
-    public void onMovedToScrapHeap(View view) {
-        unhighlightView(view);
-    }
-
-    private void highlightView(View view) {
-        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
-            // already highlighted
-        } else {
-            view.setTag(R.id.view_highlighted, true);
-            view.setTag(R.id.view_unhighlight_background, view.getBackground());
-            view.setBackground(getHighlightBackground());
-            view.postDelayed(() -> {
-                unhighlightView(view);
-            }, 15000L);
-        }
-    }
-
-    private void unhighlightView(View view) {
-        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
-            Object background = view.getTag(R.id.view_unhighlight_background);
-            if (background instanceof Drawable) {
-                view.setBackground((Drawable) background);
-            }
-            view.setTag(R.id.view_unhighlight_background, null);
-            view.setTag(R.id.view_highlighted, false);
-        }
-    }
-
-    private ColorDrawable getHighlightBackground() {
-        int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(mListView.getContext()), 26);
-        if (mColorAnimated) {
-            return new ColorDrawable(color);
-        }
-        mColorAnimated = true;
-        ColorDrawable bg = new ColorDrawable(Color.WHITE);
-        ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
-        anim.setEvaluator(new ArgbEvaluator());
-        anim.setDuration(200L);
-        anim.setRepeatMode(ValueAnimator.REVERSE);
-        anim.setRepeatCount(4);
-        anim.start();
-        return bg;
-    }
-}
diff --git a/src/com/android/launcher3/views/ButtonPreference.java b/src/com/android/launcher3/views/ButtonPreference.java
deleted file mode 100644
index fdcf2ca..0000000
--- a/src/com/android/launcher3/views/ButtonPreference.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Extension of {@link Preference} which makes the widget layout clickable.
- *
- * @see #setWidgetLayoutResource(int)
- */
-public class ButtonPreference extends Preference {
-
-    private boolean mWidgetFrameVisible = false;
-
-    public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public ButtonPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ButtonPreference(Context context) {
-        super(context);
-    }
-
-    public void setWidgetFrameVisible(boolean isVisible) {
-        if (mWidgetFrameVisible != isVisible) {
-            mWidgetFrameVisible = isVisible;
-            notifyChanged();
-        }
-    }
-
-    @Override
-    protected void onBindView(View view) {
-        super.onBindView(view);
-
-        ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
-        if (widgetFrame != null) {
-            widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
-        }
-    }
-}
diff --git a/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java b/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java
new file mode 100644
index 0000000..8d7dd4b
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java
@@ -0,0 +1,15 @@
+package com.android.systemui.plugins;
+
+import android.view.ViewGroup;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to wrap the widget on the first home screen, e.g. to add new content.
+ */
+@ProvidesInterface(action = FirstScreenWidget.ACTION, version = FirstScreenWidget.VERSION)
+public interface FirstScreenWidget extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_FIRST_SCREEN_WIDGET";
+    int VERSION = 1;
+
+    void onWidgetUpdated(ViewGroup widgetView);
+}