Merge "Adding dark theme for AddItemActivity" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index aefc1f0..4b32702 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,28 @@
     srcs: [
         "tests/tapl/**/*.java",
         "quickstep/src/com/android/quickstep/SwipeUpSetting.java",
+        "src/com/android/launcher3/util/SecureSettingsObserver.java",
         "src/com/android/launcher3/TestProtocol.java",
     ],
     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 b8e6c85..fbe19b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -53,7 +53,6 @@
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 28
 LOCAL_MODULE := LauncherPluginLib
-LOCAL_PRIVILEGED_MODULE := true
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -67,9 +66,10 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.recyclerview_recyclerview \
-    androidx.dynamicanimation_dynamicanimation
+    androidx.dynamicanimation_dynamicanimation \
+    androidx.preference_preference
 
-LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
+LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
 
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, protos) \
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c626615..1beaea5 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -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/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index ca12951..88c362d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -43,7 +43,12 @@
     }
 
     public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
-        mPluginManager.addPluginListener(listener, pluginClass);
+        addPluginListener(listener, pluginClass, false);
+    }
+
+    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
+            boolean allowMultiple) {
+        mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
     }
 
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 4417a3d..94ec69a 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -52,6 +52,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -80,6 +81,7 @@
     private final OverviewCallbacks mOverviewCallbacks;
     private final TaskOverlayFactory mTaskOverlayFactory;
     private final TouchInteractionLog mTouchInteractionLog;
+    private final InputConsumerController mInputConsumer;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -101,8 +103,8 @@
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
-            TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker,
-            TouchInteractionLog touchInteractionLog) {
+            TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
+            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) {
         super(base);
 
         mRunningTask = runningTaskInfo;
@@ -117,6 +119,7 @@
         mTaskOverlayFactory = taskOverlayFactory;
         mTouchInteractionLog = touchInteractionLog;
         mTouchInteractionLog.setTouchConsumer(this);
+        mInputConsumer = inputConsumer;
     }
 
     @Override
@@ -226,7 +229,7 @@
         RecentsAnimationState animationState = new RecentsAnimationState();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
                 animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                mTouchInteractionLog);
+                mInputConsumer, mTouchInteractionLog);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index d71c08a..27f1399 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,23 +15,21 @@
  */
 package com.android.quickstep;
 
+import static com.android.quickstep.SwipeUpSetting.newSwipeUpSettingsObserver;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -60,7 +58,7 @@
     private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
     private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
 
-    private final SwipeUpGestureEnabledSettingObserver mSwipeUpSettingObserver;
+    private final SecureSettingsObserver mSwipeUpSettingObserver;
 
     private final Context mContext;
     private final Handler mUiHandler;
@@ -85,9 +83,11 @@
         mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
 
         if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
-            mSwipeUpSettingObserver = new SwipeUpGestureEnabledSettingObserver(mUiHandler,
-                    context.getContentResolver());
+            mSwipeUpSettingObserver =
+                    newSwipeUpSettingsObserver(context, this::notifySwipeUpSettingChanged);
             mSwipeUpSettingObserver.register();
+            mSwipeUpEnabled = mSwipeUpSettingObserver.getValue();
+            resetHomeBounceSeenOnQuickstepEnabledFirstTime();
         } else {
             mSwipeUpSettingObserver = null;
             mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
@@ -192,34 +192,6 @@
                 sendToTarget();
     }
 
-    private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
-        private ContentResolver mResolver;
-        private final int defaultValue;
-
-        SwipeUpGestureEnabledSettingObserver(Handler handler, ContentResolver resolver) {
-            super(handler);
-            mResolver = resolver;
-            defaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue() ? 1 : 0;
-        }
-
-        public void register() {
-            mResolver.registerContentObserver(Settings.Secure.getUriFor(SWIPE_UP_SETTING_NAME),
-                    false, this);
-            mSwipeUpEnabled = getValue();
-            resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            notifySwipeUpSettingChanged(getValue());
-        }
-
-        private boolean getValue() {
-            return Settings.Secure.getInt(mResolver, SWIPE_UP_SETTING_NAME, defaultValue) == 1;
-        }
-    }
-
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
                 HAS_ENABLED_QUICKSTEP_ONCE, true)) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index eea3971..2f3cb5f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -53,24 +53,18 @@
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    private InputConsumerController mInputConsumer =
-            InputConsumerController.getRecentsAnimationInputConsumer();
+    private final InputConsumerController mInputConsumer;
     private final Supplier<TouchConsumer> mTouchProxySupplier;
 
-    private boolean mInputConsumerUnregistered;
-    private boolean mTouchProxyEnabled;
-
     private TouchConsumer mTouchConsumer;
     private boolean mTouchInProgress;
-    private boolean mInputConsumerUnregisterPending;
 
     private boolean mFinishPending;
 
-    public RecentsAnimationWrapper(Supplier<TouchConsumer> touchProxySupplier) {
-        // Register the input consumer on the UI thread, to ensure that it runs after any pending
-        // unregister calls
+    public RecentsAnimationWrapper(InputConsumerController inputConsumer,
+            Supplier<TouchConsumer> touchProxySupplier) {
+        mInputConsumer = inputConsumer;
         mTouchProxySupplier = touchProxySupplier;
-        mMainThreadExecutor.execute(mInputConsumer::registerInputConsumer);
     }
 
     public synchronized void setController(
@@ -109,6 +103,7 @@
     public void finish(boolean toHome, Runnable onFinishComplete) {
         if (!toHome) {
             mExecutorService.submit(() -> finishBg(false, onFinishComplete));
+            return;
         }
 
         mMainThreadExecutor.execute(() -> {
@@ -152,28 +147,12 @@
         }
     }
 
-    public void unregisterInputConsumer() {
-        mMainThreadExecutor.execute(this::unregisterInputConsumerUi);
-    }
-
-    private void unregisterInputConsumerUi() {
-        if (mTouchProxyEnabled && mTouchInProgress) {
-            mInputConsumerUnregisterPending = true;
-        } else {
-            mInputConsumerUnregistered = true;
-            mInputConsumer.unregisterInputConsumer();
-        }
-    }
-
     public void enableTouchProxy() {
         mMainThreadExecutor.execute(this::enableTouchProxyUi);
     }
 
     private void enableTouchProxyUi() {
-        if (!mInputConsumerUnregistered) {
-            mTouchProxyEnabled = true;
-            mInputConsumer.setTouchListener(this::onInputConsumerTouch);
-        }
+        mInputConsumer.setTouchListener(this::onInputConsumerTouch);
     }
 
     private boolean onInputConsumerTouch(MotionEvent ev) {
@@ -184,10 +163,6 @@
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
-            if (mInputConsumerUnregisterPending) {
-                mInputConsumerUnregisterPending = false;
-                mInputConsumer.unregisterInputConsumer();
-            }
             if (mFinishPending) {
                 mFinishPending = false;
                 mExecutorService.submit(() -> finishBg(true, null));
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
index 0f91f97..381ab9f 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpSetting.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
@@ -16,9 +16,15 @@
 
 package com.android.quickstep;
 
+import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+
+import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SecureSettingsObserver.OnChangeListener;
+
 public final class SwipeUpSetting {
     private static final String TAG = "SwipeUpSetting";
 
@@ -47,4 +53,10 @@
     public static boolean isSwipeUpEnabledDefaultValue() {
         return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
     }
+
+    public static SecureSettingsObserver newSwipeUpSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(context.getContentResolver(), listener,
+                SWIPE_UP_SETTING_NAME, isSwipeUpEnabledDefaultValue() ? 1 : 0);
+    }
 }
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 e64d04a..66ce4c3 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -64,7 +64,7 @@
     protected T mSystemShortcut;
 
     protected TaskSystemShortcut(T systemShortcut) {
-        super(systemShortcut.iconResId, systemShortcut.labelResId);
+        super(systemShortcut);
         mSystemShortcut = systemShortcut;
     }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index cded799..9371a4c 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -51,6 +51,7 @@
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ChoreographerCompat;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
 import java.io.FileDescriptor;
@@ -185,6 +186,7 @@
     private OverviewCallbacks mOverviewCallbacks;
     private TaskOverlayFactory mTaskOverlayFactory;
     private TouchInteractionLog mTouchInteractionLog;
+    private InputConsumerController mInputConsumer;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -203,6 +205,8 @@
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.get(this);
         mTouchInteractionLog = new TouchInteractionLog();
+        mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
+        mInputConsumer.registerInputConsumer();
 
         sConnected = true;
 
@@ -213,6 +217,7 @@
 
     @Override
     public void onDestroy() {
+        mInputConsumer.unregisterInputConsumer();
         mOverviewCommandHelper.onDestroy();
         sConnected = false;
         super.onDestroy();
@@ -256,7 +261,7 @@
                             mOverviewCommandHelper.overviewIntent,
                             mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
                             mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
-                            mTaskOverlayFactory, tracker, mTouchInteractionLog);
+                            mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 9991552..1c79f44 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -78,6 +78,7 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -226,8 +227,7 @@
 
     private @InteractionType int mInteractionType = INTERACTION_NORMAL;
 
-    private final RecentsAnimationWrapper mRecentsAnimationWrapper =
-            new RecentsAnimationWrapper(this::createNewTouchProxyHandler);
+    private final RecentsAnimationWrapper mRecentsAnimationWrapper;
 
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
@@ -241,7 +241,7 @@
 
     WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller,
-            TouchInteractionLog touchInteractionLog) {
+            InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
         this.id = id;
         mContext = context;
         mRunningTaskInfo = runningTaskInfo;
@@ -251,6 +251,8 @@
         mActivityInitListener = mActivityControlHelper
                 .createActivityInitListener(this::onActivityInit);
         mTouchInteractionLog = touchInteractionLog;
+        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
+                this::createNewTouchProxyHandler);
 
         initStateCallbacks();
     }
@@ -538,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) {
@@ -813,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);
@@ -860,7 +866,6 @@
         }
 
         mActivityInitListener.unregister();
-        mRecentsAnimationWrapper.unregisterInputConsumer();
         mTaskSnapshot = null;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 28928a8..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,19 +191,21 @@
         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));
         }
     }
 
     private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
         ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
                 R.layout.task_view_menu_option, this, false);
-        menuOptionView.findViewById(R.id.icon).setBackgroundResource(menuOption.iconResId);
-        ((TextView) menuOptionView.findViewById(R.id.text)).setText(menuOption.labelResId);
+        menuOption.setIconAndLabelFor(
+                menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
         menuOptionView.setOnClickListener(onClickListener);
         mOptionLayout.addView(menuOptionView);
     }
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 a0615f5..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,11 +389,14 @@
 
         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(new AccessibilityNodeInfo.AccessibilityAction(menuOption.labelResId,
-                        context.getText(menuOption.labelResId)));
+                info.addAction(menuOption.createAccessibilityAction(context));
             }
         }
 
@@ -408,8 +416,12 @@
             return true;
         }
 
-        for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
-            if (action == menuOption.labelResId) {
+        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);
                 if (onClickListener != null) {
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 9aff1ff..7de4388 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -65,10 +65,10 @@
     <string name="folder_hint_text" msgid="6617836969016293992">"תיקיה ללא שם"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> מושבתת"</string>
     <plurals name="badged_app_label" formatted="false" msgid="7948068486082879291">
-      <item quantity="two">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
-      <item quantity="many">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
-      <item quantity="other">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> הודעות</item>
-      <item quantity="one">לאפליקציה <xliff:g id="APP_NAME_0">%1$s</xliff:g> יש הודעה אחת (<xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g>)</item>
+      <item quantity="two">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
+      <item quantity="many">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
+      <item quantity="other">לאפליקציה <xliff:g id="APP_NAME_2">%1$s</xliff:g> יש <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> התראות</item>
+      <item quantity="one">לאפליקציה <xliff:g id="APP_NAME_0">%1$s</xliff:g> יש התראה אחת (<xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g>)</item>
     </plurals>
     <string name="default_scroll_format" msgid="7475544710230993317">"‏דף %1$d מתוך %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"‏מסך דף הבית %1$d מתוך %2$d"</string>
@@ -85,11 +85,11 @@
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"הושבת על ידי מנהל המערכת שלך"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"אפשרות סיבוב של מסך דף הבית"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"כאשר הטלפון מסובב"</string>
-    <string name="icon_badging_title" msgid="874121399231955394">"סימני הודעות"</string>
+    <string name="icon_badging_title" msgid="874121399231955394">"סימני ההתראות"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"מופעלת"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"כבויה"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"נדרשת גישה להודעות"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"כדי להציג את סימני ההודעות, יש להפעיל הודעות מהאפליקציה <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"נדרשת גישה להתראות"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"כדי להציג את סימני ההתראות,יש להפעיל התראות מהאפליקציה <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"שנה את ההגדרות"</string>
     <string name="icon_badging_service_title" msgid="2309733118428242174">"הצגה של סימן ההודעות"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"הוספת סמל במסך דף הבית"</string>
@@ -137,7 +137,7 @@
     <string name="action_deep_shortcut" msgid="2864038805849372848">"קיצורי דרך"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"קיצורי דרך והודעות"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"סגור"</string>
-    <string name="notification_dismissed" msgid="6002233469409822874">"ההודעה נסגרה"</string>
+    <string name="notification_dismissed" msgid="6002233469409822874">"ההתראה נסגרה"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישיות"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index f400832..d5188c9 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -73,7 +73,7 @@
     <string name="workspace_new_page" msgid="257366611030256142">"ပင်မမျက်နှာပြင် စာမျက်နှာသစ်"</string>
     <string name="folder_opened" msgid="94695026776264709">"ဖွင့်ထားသောအကန့်, <xliff:g id="WIDTH">%1$d</xliff:g> နှင့် <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ဖိုင်တွဲကို ပိတ်ရန် တို့ပါ"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"အမည်ပြောင်းခြင်းကို သိမ်းဆည်းရန် တို့ပါ"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"အမည်ပြောင်းခြင်းကို သိမ်းရန် တို့ပါ"</string>
     <string name="folder_closed" msgid="4100806530910930934">"ပိတ်ထားသောအကန့်"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"ပြောင်းလဲလိုက်သော အကန့်အမည် <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"အကန့်အမည်: <xliff:g id="NAME">%1$s</xliff:g>"</string>
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/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/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index b02182c..6bf5812 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
@@ -33,7 +33,7 @@
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.SettingsObserver;
+import com.android.launcher3.util.SecureSettingsObserver;
 
 public class LauncherAppState {
 
@@ -48,7 +48,7 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final SettingsObserver mNotificationBadgingObserver;
+    private final SecureSettingsObserver mNotificationBadgingObserver;
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -99,17 +99,17 @@
             mNotificationBadgingObserver = null;
         } else {
             // Register an observer to rebind the notification listener when badging is re-enabled.
-            mNotificationBadgingObserver = new SettingsObserver.Secure(
-                    mContext.getContentResolver()) {
-                @Override
-                public void onSettingChanged(boolean isNotificationBadgingEnabled) {
-                    if (isNotificationBadgingEnabled) {
-                        NotificationListener.requestRebind(new ComponentName(
-                                mContext, NotificationListener.class));
-                    }
-                }
-            };
-            mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
+            mNotificationBadgingObserver =
+                    newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
+            mNotificationBadgingObserver.register();
+            mNotificationBadgingObserver.dispatchOnChange();
+        }
+    }
+
+    protected void onNotificationSettingsChanged(boolean isNotificationBadgingEnabled) {
+        if (isNotificationBadgingEnabled) {
+            NotificationListener.requestRebind(new ComponentName(
+                    mContext, NotificationListener.class));
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index cbfde25..8e9021f 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -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 60edcda..0000000
--- a/src/com/android/launcher3/SettingsActivity.java
+++ /dev/null
@@ -1,327 +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 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.app.FragmentManager;
-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.SettingsObserver;
-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.NOTIFICATION_BADGING */
-    public static final String NOTIFICATION_BADGING = "notification_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 IconBadgingObserver 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 = new IconBadgingObserver(
-                        iconBadgingPref, resolver, getFragmentManager());
-                mIconBadgingObserver.register(NOTIFICATION_BADGING, NOTIFICATION_ENABLED_LISTENERS);
-            }
-
-            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 extends SettingsObserver.Secure {
-
-        private final ButtonPreference mBadgingPref;
-        private final ContentResolver mResolver;
-        private final FragmentManager mFragmentManager;
-
-        public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver,
-                FragmentManager fragmentManager) {
-            super(resolver);
-            mBadgingPref = badgingPref;
-            mResolver = resolver;
-            mFragmentManager = fragmentManager;
-        }
-
-        @Override
-        public void onSettingChanged(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/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 5348349..90e195b 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -25,16 +25,22 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertySetter;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.AllAppsRow;
+import com.android.systemui.plugins.PluginListener;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class FloatingHeaderView extends LinearLayout implements
-        ValueAnimator.AnimatorUpdateListener {
+        ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow> {
 
     private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
     private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
@@ -64,6 +70,9 @@
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
     private AllAppsRecyclerView mCurrentRV;
+    protected final Map<AllAppsRow, View> mPluginRows;
+    // Contains just the values of the above map so we can iterate without extracting a new list.
+    protected final List<View> mPluginRowViews;
     private ViewGroup mParent;
     private boolean mHeaderCollapsed;
     private int mSnappedScrolledY;
@@ -82,6 +91,8 @@
 
     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
+        mPluginRows = new HashMap<>();
+        mPluginRowViews = new ArrayList<>();
     }
 
     @Override
@@ -90,6 +101,38 @@
         mTabLayout = findViewById(R.id.tabs);
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(this,
+                AllAppsRow.class, true /* allowMultiple */);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
+    }
+
+    @Override
+    public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
+        mPluginRows.put(allAppsRowPlugin, null);
+        setupPluginRows();
+        allAppsRowPlugin.setOnHeightUpdatedListener(this::onPluginRowHeightUpdated);
+    }
+
+    protected void onPluginRowHeightUpdated() {
+    }
+
+    @Override
+    public void onPluginDisconnected(AllAppsRow plugin) {
+        View pluginRowView = mPluginRows.get(plugin);
+        removeView(pluginRowView);
+        mPluginRows.remove(plugin);
+        mPluginRowViews.remove(pluginRowView);
+        onPluginRowHeightUpdated();
+    }
+
     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
         mTabsHidden = tabsHidden;
         mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
@@ -97,9 +140,24 @@
         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
         mParent = (ViewGroup) mMainRV.getParent();
         setMainActive(mMainRVActive || mWorkRV == null);
+        setupPluginRows();
         reset(false);
     }
 
+    private void setupPluginRows() {
+        for (Map.Entry<AllAppsRow, View> rowPluginEntry : mPluginRows.entrySet()) {
+            if (rowPluginEntry.getValue() == null) {
+                View pluginRow = rowPluginEntry.getKey().setup(this);
+                addView(pluginRow, indexOfChild(mTabLayout));
+                rowPluginEntry.setValue(pluginRow);
+                mPluginRowViews.add(pluginRow);
+            }
+        }
+        for (View plugin : mPluginRowViews) {
+            plugin.setVisibility(mHeaderCollapsed ? GONE : VISIBLE);
+        }
+    }
+
     private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
         if (old != updated && updated != null ) {
             updated.addOnScrollListener(mOnScrollListener);
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/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 6d1efd5..b6a8b50 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, null, 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 6433103..9198c24 100644
--- a/src/com/android/launcher3/icons/BaseIconCache.java
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -261,7 +261,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
new file mode 100644
index 0000000..c8c9618
--- /dev/null
+++ b/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -0,0 +1,303 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.R;
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
+
+/**
+ * This class will be moved to androidx library. There shouldn't be any dependency outside
+ * this package.
+ */
+public class BaseIconFactory {
+
+    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+    public static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+
+    private final Rect mOldBounds = new Rect();
+    private final Context mContext;
+    private final Canvas mCanvas;
+    private final PackageManager mPm;
+    private final ColorExtractor mColorExtractor;
+    private boolean mDisableColorExtractor;
+
+    private int mFillResIconDpi;
+    private int mIconBitmapSize;
+
+    private IconNormalizer mNormalizer;
+    private ShadowGenerator mShadowGenerator;
+
+    private Drawable mWrapperIcon;
+    private int mWrapperBackgroundColor;
+
+    protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+        mContext = context.getApplicationContext();
+
+        mFillResIconDpi = fillResIconDpi;
+        mIconBitmapSize = iconBitmapSize;
+
+        mPm = mContext.getPackageManager();
+        mColorExtractor = new ColorExtractor();
+
+        mCanvas = new Canvas();
+        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+    }
+
+    protected void clear() {
+        mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+        mDisableColorExtractor = false;
+    }
+
+    public ShadowGenerator getShadowGenerator() {
+        if (mShadowGenerator == null) {
+            mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
+        }
+        return mShadowGenerator;
+    }
+
+    public IconNormalizer getNormalizer() {
+        if (mNormalizer == null) {
+            mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
+        }
+        return mNormalizer;
+    }
+
+    public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
+        try {
+            Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
+            if (resources != null) {
+                final int id = resources.getIdentifier(iconRes.resourceName, null, null);
+                // do not stamp old legacy shortcuts as the app may have already forgotten about it
+                return createBadgedIconBitmap(
+                        resources.getDrawableForDensity(id, mFillResIconDpi),
+                        Process.myUserHandle() /* only available on primary user */,
+                        false /* do not apply legacy treatment */);
+            }
+        } catch (Exception e) {
+            // Icon not found.
+        }
+        return null;
+    }
+
+    public BitmapInfo createIconBitmap(Bitmap icon) {
+        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
+            return BitmapInfo.fromBitmap(icon);
+        }
+        return BitmapInfo.fromBitmap(
+                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
+    }
+
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons) {
+        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
+    }
+
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons, boolean isInstantApp) {
+        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, null);
+    }
+
+    /**
+     * Creates bitmap using the source drawable and various parameters.
+     * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
+     *
+     * @param icon                      source of the icon
+     * @param user                      info can be used for a badge
+     * @param shrinkNonAdaptiveIcons    {@code true} if non adaptive icons should be treated
+     * @param isInstantApp              info can be used for a badge
+     * @param scale                     returns the scale result from normalization
+     * @return a bitmap suitable for disaplaying as an icon at various system UIs.
+     */
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
+        if (scale == null) {
+            scale = new float[1];
+        }
+        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
+        Bitmap bitmap = createIconBitmap(icon, scale[0]);
+        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            mCanvas.setBitmap(bitmap);
+            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+            mCanvas.setBitmap(null);
+        }
+
+        final Bitmap result;
+        if (user != null && !Process.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
+            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
+            if (badged instanceof BitmapDrawable) {
+                result = ((BitmapDrawable) badged).getBitmap();
+            } else {
+                result = createIconBitmap(badged, 1f);
+            }
+        } else if (isInstantApp) {
+            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+            result = bitmap;
+        } else {
+            result = bitmap;
+        }
+        return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
+    }
+
+    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
+        RectF iconBounds = new RectF();
+        float[] scale = new float[1];
+        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
+        return createIconBitmap(icon,
+                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+    }
+
+    /**
+     * Sets the background color used for wrapped adaptive icon
+     */
+    public void setWrapperBackgroundColor(int color) {
+        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+    }
+
+    /**
+     * Disables the dominant color extraction for all icons loaded.
+     */
+    public void disableColorExtraction() {
+        mDisableColorExtractor = true;
+    }
+
+    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
+            RectF outIconBounds, float[] outScale) {
+        float scale = 1f;
+
+        if (shrinkNonAdaptiveIcons) {
+            boolean[] outShape = new boolean[1];
+            if (mWrapperIcon == null) {
+                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+                        .mutate();
+            }
+            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)) {
+                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+                fsd.setDrawable(icon);
+                fsd.setScale(scale);
+                icon = dr;
+                scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+
+                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
+            }
+        } else {
+            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+        }
+
+        outScale[0] = scale;
+        return icon;
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+     */
+    public void badgeWithDrawable(Bitmap target, Drawable badge) {
+        mCanvas.setBitmap(target);
+        badgeWithDrawable(mCanvas, badge);
+        mCanvas.setBitmap(null);
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+     */
+    public void badgeWithDrawable(Canvas target, Drawable badge) {
+        int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+                mIconBitmapSize, mIconBitmapSize);
+        badge.draw(target);
+    }
+
+    /**
+     * @param scale the scale to apply before drawing {@param icon} on the canvas
+     */
+    private Bitmap createIconBitmap(Drawable icon, float scale) {
+        Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
+                Bitmap.Config.ARGB_8888);
+        mCanvas.setBitmap(bitmap);
+        mOldBounds.set(icon.getBounds());
+
+        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
+                    Math.round(mIconBitmapSize * (1 - scale) / 2 ));
+            icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
+            icon.draw(mCanvas);
+        } else {
+            if (icon instanceof BitmapDrawable) {
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+                Bitmap b = bitmapDrawable.getBitmap();
+                if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
+                    bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
+                }
+            }
+            int width = mIconBitmapSize;
+            int height = mIconBitmapSize;
+
+            int intrinsicWidth = icon.getIntrinsicWidth();
+            int intrinsicHeight = icon.getIntrinsicHeight();
+            if (intrinsicWidth > 0 && intrinsicHeight > 0) {
+                // Scale the icon proportionally to the icon dimensions
+                final float ratio = (float) intrinsicWidth / intrinsicHeight;
+                if (intrinsicWidth > intrinsicHeight) {
+                    height = (int) (width / ratio);
+                } else if (intrinsicHeight > intrinsicWidth) {
+                    width = (int) (height * ratio);
+                }
+            }
+            final int left = (mIconBitmapSize - width) / 2;
+            final int top = (mIconBitmapSize - height) / 2;
+            icon.setBounds(left, top, left + width, top + height);
+            mCanvas.save();
+            mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
+            icon.draw(mCanvas);
+            mCanvas.restore();
+
+        }
+        icon.setBounds(mOldBounds);
+        mCanvas.setBitmap(null);
+        return bitmap;
+    }
+
+    /**
+     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+     * This allows the badging to be done based on the action bitmap size rather than
+     * the scaled bitmap size.
+     */
+    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+        public FixedSizeBitmapDrawable(Bitmap bitmap) {
+            super(null, bitmap);
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return getBitmap().getWidth();
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return getBitmap().getWidth();
+        }
+    }
+}
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..4052a55 100644
--- a/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/src/com/android/launcher3/icons/IconNormalizer.java
@@ -28,14 +28,9 @@
 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;
 
 import androidx.annotation.NonNull;
@@ -84,9 +79,9 @@
     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];
@@ -193,17 +188,13 @@
      */
     public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
             @Nullable Path path, @Nullable boolean[] outMaskShape) {
-        if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+        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();
@@ -314,7 +305,7 @@
         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/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 244654b..69614a3 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -16,29 +16,11 @@
 
 package com.android.launcher3.icons;
 
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.Intent.ShortcutIconResource;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
@@ -49,7 +31,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -60,14 +41,14 @@
 import androidx.annotation.Nullable;
 
 /**
- * Helper methods for generating various launcher icons
+ * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
+ * that are threadsafe.
  */
-public class LauncherIcons implements AutoCloseable {
+public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
 
-    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
-
-    public static final Object sPoolSync = new Object();
+    private static final Object sPoolSync = new Object();
     private static LauncherIcons sPool;
+    private LauncherIcons next;
 
     /**
      * Return a new Message instance from the global pool. Allows us to
@@ -82,7 +63,8 @@
                 return m;
             }
         }
-        return new LauncherIcons(context);
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+        return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize);
     }
 
     /**
@@ -91,8 +73,7 @@
     public void recycle() {
         synchronized (sPoolSync) {
             // Clear any temporary state variables
-            mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-            mDisableColorExtractor = false;
+            clear();
 
             next = sPool;
             sPool = this;
@@ -104,259 +85,41 @@
         recycle();
     }
 
-    private final Rect mOldBounds = new Rect();
     private final Context mContext;
-    private final Canvas mCanvas;
-    private final PackageManager mPm;
-    private final ColorExtractor mColorExtractor;
-    private boolean mDisableColorExtractor;
-
     private final int mFillResIconDpi;
     private final int mIconBitmapSize;
 
-    private IconNormalizer mNormalizer;
-    private ShadowGenerator mShadowGenerator;
-
-    private Drawable mWrapperIcon;
-    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-
-    // sometimes we store linked lists of these things
-    private LauncherIcons next;
-
-    private LauncherIcons(Context context) {
+    private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize) {
+        super(context, fillResIconDpi, iconBitmapSize);
         mContext = context.getApplicationContext();
-        mPm = mContext.getPackageManager();
-        mColorExtractor = new ColorExtractor();
-
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
-        mFillResIconDpi = idp.fillResIconDpi;
-        mIconBitmapSize = idp.iconBitmapSize;
-
-        mCanvas = new Canvas();
-        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+        mFillResIconDpi = fillResIconDpi;
+        mIconBitmapSize = iconBitmapSize;
     }
 
-    public ShadowGenerator getShadowGenerator() {
-        if (mShadowGenerator == null) {
-            mShadowGenerator = new ShadowGenerator(mContext);
-        }
-        return mShadowGenerator;
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            int iconAppTargetSdk) {
+        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
     }
 
-    public IconNormalizer getNormalizer() {
-        if (mNormalizer == null) {
-            mNormalizer = new IconNormalizer(mContext);
-        }
-        return mNormalizer;
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view. If the package or the resource do not
-     * exist, it returns null.
-     */
-    public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
-        try {
-            Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
-            if (resources != null) {
-                final int id = resources.getIdentifier(iconRes.resourceName, null, null);
-                // do not stamp old legacy shortcuts as the app may have already forgotten about it
-                return createBadgedIconBitmap(
-                        resources.getDrawableForDensity(id, mFillResIconDpi),
-                        Process.myUserHandle() /* only available on primary user */,
-                        0 /* do not apply legacy treatment */);
-            }
-        } catch (Exception e) {
-            // Icon not found.
-        }
-        return null;
-    }
-
-    /**
-     * Returns a bitmap which is of the appropriate size to be displayed as an icon
-     */
-    public BitmapInfo createIconBitmap(Bitmap icon) {
-        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
-            return BitmapInfo.fromBitmap(icon);
-        }
-        return BitmapInfo.fromBitmap(
-                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
-    }
-
-    /**
-     * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
-     * view or workspace. The icon is badged for {@param user}.
-     * The bitmap is also visually normalized with other icons.
-     */
-    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
-        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false, null);
-    }
-
-    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
-            boolean isInstantApp) {
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            int iconAppTargetSdk, boolean isInstantApp) {
         return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
     }
 
-    /**
-     * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
-     * view or workspace. The icon is badged for {@param user}.
-     * The bitmap is also visually normalized with other icons.
-     */
-    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
-            boolean isInstantApp, float[] scale) {
-        if (scale == null) {
-            scale = new float[1];
-        }
-        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
-        Bitmap bitmap = createIconBitmap(icon, scale[0]);
-        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
-            mCanvas.setBitmap(bitmap);
-            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
-            mCanvas.setBitmap(null);
-        }
-
-        final Bitmap result;
-        if (user != null && !Process.myUserHandle().equals(user)) {
-            BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
-            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
-            if (badged instanceof BitmapDrawable) {
-                result = ((BitmapDrawable) badged).getBitmap();
-            } else {
-                result = createIconBitmap(badged, 1f);
-            }
-        } else if (isInstantApp) {
-            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
-            result = bitmap;
-        } else {
-            result = bitmap;
-        }
-        return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
+        boolean shrinkNonAdaptiveIcons = Utilities.ATLEAST_P ||
+                (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
+        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
     }
 
-    /**
-     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
-     * normalized with other icons and has enough spacing to add shadow.
-     */
     public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
-        RectF iconBounds = new RectF();
-        float[] scale = new float[1];
-        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
-        return createIconBitmap(icon,
-                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+        boolean shrinkNonAdaptiveIcons = Utilities.ATLEAST_P ||
+                (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
+        return  createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
     }
 
-    /**
-     * Sets the background color used for wrapped adaptive icon
-     */
-    public void setWrapperBackgroundColor(int color) {
-        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
-    }
-
-    /**
-     * Disables the dominant color extraction for all icons loaded through this session (until
-     * this instance is recycled).
-     */
-    public void disableColorExtraction() {
-        mDisableColorExtractor = true;
-    }
-
-    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
-            RectF outIconBounds, float[] outScale) {
-        float scale = 1f;
-        if ((Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) ||
-                Utilities.ATLEAST_P) {
-            boolean[] outShape = new boolean[1];
-            if (mWrapperIcon == null) {
-                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
-                        .mutate();
-            }
-            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
-            dr.setBounds(0, 0, 1, 1);
-            scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
-            if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
-                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
-                fsd.setDrawable(icon);
-                fsd.setScale(scale);
-                icon = dr;
-                scale = getNormalizer().getScale(icon, outIconBounds, null, null);
-
-                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
-            }
-        } else {
-            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
-        }
-
-        outScale[0] = scale;
-        return icon;
-    }
-
-    /**
-     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
-     */
-    public void badgeWithDrawable(Bitmap target, Drawable badge) {
-        mCanvas.setBitmap(target);
-        badgeWithDrawable(mCanvas, badge);
-        mCanvas.setBitmap(null);
-    }
-
-    /**
-     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
-     */
-    private void badgeWithDrawable(Canvas target, Drawable badge) {
-        int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
-        badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
-                mIconBitmapSize, mIconBitmapSize);
-        badge.draw(target);
-    }
-
-    /**
-     * @param scale the scale to apply before drawing {@param icon} on the canvas
-     */
-    private Bitmap createIconBitmap(Drawable icon, float scale) {
-        Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
-                Bitmap.Config.ARGB_8888);
-        mCanvas.setBitmap(bitmap);
-        mOldBounds.set(icon.getBounds());
-
-        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
-            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
-                    Math.round(mIconBitmapSize * (1 - scale) / 2 ));
-            icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
-            icon.draw(mCanvas);
-        } else {
-            if (icon instanceof BitmapDrawable) {
-                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-                Bitmap b = bitmapDrawable.getBitmap();
-                if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
-                    bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
-                }
-            }
-            int width = mIconBitmapSize;
-            int height = mIconBitmapSize;
-
-            int intrinsicWidth = icon.getIntrinsicWidth();
-            int intrinsicHeight = icon.getIntrinsicHeight();
-            if (intrinsicWidth > 0 && intrinsicHeight > 0) {
-                // Scale the icon proportionally to the icon dimensions
-                final float ratio = (float) intrinsicWidth / intrinsicHeight;
-                if (intrinsicWidth > intrinsicHeight) {
-                    height = (int) (width / ratio);
-                } else if (intrinsicHeight > intrinsicWidth) {
-                    width = (int) (height * ratio);
-                }
-            }
-            final int left = (mIconBitmapSize - width) / 2;
-            final int top = (mIconBitmapSize - height) / 2;
-            icon.setBounds(left, top, left + width, top + height);
-            mCanvas.save();
-            mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
-            icon.draw(mCanvas);
-            mCanvas.restore();
-
-        }
-        icon.setBounds(mOldBounds);
-        mCanvas.setBitmap(null);
-        return bitmap;
-    }
+    // below methods should also migrate to BaseIconFactory
 
     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
         return createShortcutIcon(shortcutInfo, true /* badged */);
@@ -424,26 +187,4 @@
             return pkgInfo;
         }
     }
-
-    /**
-     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
-     * This allows the badging to be done based on the action bitmap size rather than
-     * the scaled bitmap size.
-     */
-    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
-        public FixedSizeBitmapDrawable(Bitmap bitmap) {
-            super(null, bitmap);
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return getBitmap().getWidth();
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return getBitmap().getWidth();
-        }
-    }
 }
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/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/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1ec7af2..405125e 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)) {
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..47fcd9e 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);
                 }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 9f02d4f..40c1912 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;
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 68cf7de..f27b728 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.notification;
 
-import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.annotation.TargetApi;
 import android.app.Notification;
@@ -28,14 +28,13 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.SettingsObserver;
+import com.android.launcher3.util.SecureSettingsObserver;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,7 +42,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import androidx.annotation.Nullable;
 
@@ -79,7 +77,7 @@
     /** The last notification key that was dismissed from launcher UI */
     private String mLastKeyDismissedByLauncher;
 
-    private SettingsObserver mNotificationBadgingObserver;
+    private SecureSettingsObserver mNotificationBadgingObserver;
 
     private final Handler.Callback mWorkerCallback = new Handler.Callback() {
         @Override
@@ -196,19 +194,20 @@
         super.onListenerConnected();
         sIsConnected = true;
 
-        mNotificationBadgingObserver = new SettingsObserver.Secure(getContentResolver()) {
-            @Override
-            public void onSettingChanged(boolean isNotificationBadgingEnabled) {
-                if (!isNotificationBadgingEnabled && sIsConnected) {
-                    requestUnbind();
-                }
-            }
-        };
-        mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
+        mNotificationBadgingObserver =
+                newNotificationSettingsObserver(this, this::onNotificationBadgingChanged);
+        mNotificationBadgingObserver.register();
+        mNotificationBadgingObserver.dispatchOnChange();
 
         onNotificationFullRefresh();
     }
 
+    private void onNotificationBadgingChanged(boolean isNotificationBadgingEnabled) {
+        if (!isNotificationBadgingEnabled && sIsConnected) {
+            requestUnbind();
+        }
+    }
+
     private void onNotificationFullRefresh() {
         mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget();
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 6877cc4..b9e6a98 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -391,13 +391,10 @@
         if (view instanceof DeepShortcutView) {
             // Expanded system shortcut, with both icon and text shown on white background.
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
-            shortcutView.getIconView().setBackgroundResource(info.iconResId);
-            shortcutView.getBubbleText().setText(info.labelResId);
+            info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
         } else if (view instanceof ImageView) {
             // Only the system shortcut icon shows on a gray background header.
-            final ImageView shortcutIcon = (ImageView) view;
-            shortcutIcon.setImageResource(info.iconResId);
-            shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
+            info.setIconAndContentDescriptionFor((ImageView) view);
         }
         view.setTag(info);
         view.setOnClickListener(info.getOnClickListener(mLauncher,
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index c14c00e..61113b8 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -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..af0d3da
--- /dev/null
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -0,0 +1,72 @@
+/*
+ * 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.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(0,
+                        (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 693e532..f9a2007 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -3,10 +3,17 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
+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;
+import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
@@ -23,18 +30,86 @@
 import java.util.List;
 
 /**
- * Represents a system shortcut for a given app. The shortcut should have a static label and
- * icon, and an onClickListener that depends on the item that the shortcut services.
+ * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
+ * onClickListener that depends on the item that the shortcut services.
  *
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
  */
 public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
-    public final int iconResId;
-    public final int labelResId;
+    private final int mIconResId;
+    private final int mLabelResId;
+    private final Icon mIcon;
+    private final CharSequence mLabel;
+    private final CharSequence mContentDescription;
+    private final int mAccessibilityActionId;
 
     public SystemShortcut(int iconResId, int labelResId) {
-        this.iconResId = iconResId;
-        this.labelResId = labelResId;
+        mIconResId = iconResId;
+        mLabelResId = labelResId;
+        mAccessibilityActionId = labelResId;
+        mIcon = null;
+        mLabel = null;
+        mContentDescription = null;
+    }
+
+    public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
+            int accessibilityActionId) {
+        mIcon = icon;
+        mLabel = label;
+        mContentDescription = contentDescription;
+        mAccessibilityActionId = accessibilityActionId;
+        mIconResId = 0;
+        mLabelResId = 0;
+    }
+
+    public SystemShortcut(SystemShortcut other) {
+        mIconResId = other.mIconResId;
+        mLabelResId = other.mLabelResId;
+        mIcon = other.mIcon;
+        mLabel = other.mLabel;
+        mContentDescription = other.mContentDescription;
+        mAccessibilityActionId = other.mAccessibilityActionId;
+    }
+
+    public void setIconAndLabelFor(View iconView, TextView labelView) {
+        if (mIcon != null) {
+            mIcon.loadDrawableAsync(iconView.getContext(),
+                    iconView::setBackground,
+                    new Handler(Looper.getMainLooper()));
+        } else {
+            iconView.setBackgroundResource(mIconResId);
+        }
+
+        if (mLabel != null) {
+            labelView.setText(mLabel);
+        } else {
+            labelView.setText(mLabelResId);
+        }
+    }
+
+    public void setIconAndContentDescriptionFor(ImageView view) {
+        if (mIcon != null) {
+            mIcon.loadDrawableAsync(view.getContext(),
+                    view::setImageDrawable,
+                    new Handler(Looper.getMainLooper()));
+        } else {
+            view.setImageResource(mIconResId);
+        }
+
+        view.setContentDescription(getContentDescription(view.getContext()));
+    }
+
+    private CharSequence getContentDescription(Context context) {
+        return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
+    }
+
+    public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
+        return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
+                getContentDescription(context));
+    }
+
+    public boolean hasHandlerForAction(int action) {
+        return mAccessibilityActionId == action;
     }
 
     public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
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..66420d0
--- /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();
+            }
+
+            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/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/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
new file mode 100644
index 0000000..48aa02b
--- /dev/null
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+/**
+ * Utility class to listen for secure settings changes
+ */
+public class SecureSettingsObserver extends ContentObserver {
+
+    /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
+    public static final String NOTIFICATION_BADGING = "notification_badging";
+
+    private final ContentResolver mResolver;
+    private final String mKeySetting;
+    private final int mDefaultValue;
+    private final OnChangeListener mOnChangeListener;
+
+    public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener,
+            String keySetting, int defaultValue) {
+        super(new Handler());
+
+        mResolver = resolver;
+        mOnChangeListener = listener;
+        mKeySetting = keySetting;
+        mDefaultValue = defaultValue;
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        mOnChangeListener.onSettingsChanged(getValue());
+    }
+
+    public boolean getValue() {
+        return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1;
+    }
+
+    public void register() {
+        mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this);
+    }
+
+    public ContentResolver getResolver() {
+        return mResolver;
+    }
+
+    public void dispatchOnChange() {
+        onChange(true);
+    }
+
+    public void unregister() {
+        mResolver.unregisterContentObserver(this);
+    }
+
+    public interface OnChangeListener {
+        void onSettingsChanged(boolean isEnabled);
+    }
+
+    public static SecureSettingsObserver newNotificationSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
+    }
+}
diff --git a/src/com/android/launcher3/util/SettingsObserver.java b/src/com/android/launcher3/util/SettingsObserver.java
deleted file mode 100644
index 6baa242..0000000
--- a/src/com/android/launcher3/util/SettingsObserver.java
+++ /dev/null
@@ -1,100 +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.util;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-public interface SettingsObserver {
-
-    /**
-     * Registers the content observer to call {@link #onSettingChanged(boolean)} when any of the
-     * passed settings change. The value passed to onSettingChanged() is based on the key setting.
-     */
-    void register(String keySetting, String ... dependentSettings);
-    void unregister();
-    void onSettingChanged(boolean keySettingEnabled);
-
-
-    abstract class Secure extends ContentObserver implements SettingsObserver {
-        private ContentResolver mResolver;
-        private String mKeySetting;
-
-        public Secure(ContentResolver resolver) {
-            super(new Handler());
-            mResolver = resolver;
-        }
-
-        @Override
-        public void register(String keySetting, String ... dependentSettings) {
-            mKeySetting = keySetting;
-            mResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(mKeySetting), false, this);
-            for (String setting : dependentSettings) {
-                mResolver.registerContentObserver(
-                        Settings.Secure.getUriFor(setting), false, this);
-            }
-            onChange(true);
-        }
-
-        @Override
-        public void unregister() {
-            mResolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            onSettingChanged(Settings.Secure.getInt(mResolver, mKeySetting, 1) == 1);
-        }
-    }
-
-    abstract class System extends ContentObserver implements SettingsObserver {
-        private ContentResolver mResolver;
-        private String mKeySetting;
-
-        public System(ContentResolver resolver) {
-            super(new Handler());
-            mResolver = resolver;
-        }
-
-        @Override
-        public void register(String keySetting, String ... dependentSettings) {
-            mKeySetting = keySetting;
-            mResolver.registerContentObserver(
-                    Settings.System.getUriFor(mKeySetting), false, this);
-            for (String setting : dependentSettings) {
-                mResolver.registerContentObserver(
-                        Settings.System.getUriFor(setting), false, this);
-            }
-            onChange(true);
-        }
-
-        @Override
-        public void unregister() {
-            mResolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            onSettingChanged(Settings.System.getInt(mResolver, mKeySetting, 1) == 1);
-        }
-    }
-}
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/AllAppsRow.java b/src_plugins/com/android/systemui/plugins/AllAppsRow.java
new file mode 100644
index 0000000..c003fc1
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/AllAppsRow.java
@@ -0,0 +1,51 @@
+/*
+ * 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.systemui.plugins;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a row of views to the top of the all apps drawer.
+ */
+@ProvidesInterface(action = AllAppsRow.ACTION, version = AllAppsRow.VERSION)
+public interface AllAppsRow extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_ACTIONS";
+    int VERSION = 1;
+
+    /**
+     * Setup the row and return the parent view.
+     * @param parent The ViewGroup to which launcher will add this row.
+     */
+    View setup(ViewGroup parent);
+
+    /**
+     * @return The height to reserve in all apps for your views.
+     */
+    int getExpectedHeight();
+
+    /**
+     * Update launcher whenever {@link #getExpectedHeight()} changes.
+     */
+    void setOnHeightUpdatedListener(OnHeightUpdatedListener onHeightUpdatedListener);
+
+    interface OnHeightUpdatedListener {
+        void onHeightUpdated();
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index fcb2abe..31dbb34 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -31,6 +31,10 @@
     public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
     }
 
+    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
+            boolean allowMultiple) {
+    }
+
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
     }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 7cba33a..d808873 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,7 +27,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
   ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
-  ../src/com/android/launcher3/TestProtocol.java
+  ../src/com/android/launcher3/util/SecureSettingsObserver.java \
+  ../src/com/android/launcher3/TestProtocol.java \
 
 LOCAL_SDK_VERSION := current
 LOCAL_MODULE := ub-launcher-aosp-tapl