Merge "Removing layout handling in DeviceProfile and moving it to individual views" into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/TaskMenuView.java
index 70542c2..bf75376 100644
--- a/quickstep/src/com/android/quickstep/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/TaskMenuView.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -61,6 +60,8 @@
     private Launcher mLauncher;
     private TextView mTaskIconAndName;
     private AnimatorSet mOpenCloseAnimator;
+    private TaskView mTaskView;
+    private View mWidgetsOptionView;
 
     public TaskMenuView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -130,8 +131,9 @@
             return false;
         }
         mLauncher.getDragLayer().addView(this);
-        addMenuOptions(taskView.getTask());
-        orientAroundTaskView(taskView);
+        mTaskView = taskView;
+        addMenuOptions(mTaskView.getTask());
+        orientAroundTaskView(mTaskView);
         post(this::animateOpen);
         return true;
     }
@@ -143,20 +145,27 @@
         mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
         mTaskIconAndName.setText(TaskUtils.getTitle(mLauncher, task));
 
-        LayoutInflater inflater = mLauncher.getLayoutInflater();
         for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
             OnClickListener onClickListener = menuOption.getOnClickListener(mLauncher, task);
             if (onClickListener != null) {
-                DeepShortcutView menuOptionView = (DeepShortcutView) inflater.inflate(
-                        R.layout.system_shortcut, this, false);
-                menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
-                menuOptionView.getBubbleText().setText(menuOption.labelResId);
-                menuOptionView.setOnClickListener(onClickListener);
-                addView(menuOptionView);
+                addMenuOption(menuOption, onClickListener);
             }
         }
     }
 
+    private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
+        DeepShortcutView menuOptionView = (DeepShortcutView) mLauncher.getLayoutInflater().inflate(
+                R.layout.system_shortcut, this, false);
+        menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
+        menuOptionView.getBubbleText().setText(menuOption.labelResId);
+        menuOptionView.setOnClickListener(onClickListener);
+        addView(menuOptionView);
+
+        if (menuOption instanceof TaskSystemShortcut.Widgets) {
+            mWidgetsOptionView = menuOptionView;
+        }
+    }
+
     private void orientAroundTaskView(TaskView taskView) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
@@ -220,4 +229,19 @@
             }
         };
     }
+
+    @Override
+    protected void onWidgetsBound() {
+        TaskSystemShortcut widgetsOption = new TaskSystemShortcut.Widgets();
+        View.OnClickListener onClickListener = widgetsOption.getOnClickListener(
+                mLauncher, mTaskView.getTask());
+
+        if (onClickListener != null && mWidgetsOptionView == null) {
+            // We didn't have any widgets cached but now there are some, so add the option.
+            addMenuOption(widgetsOption, onClickListener);
+        } else if (onClickListener == null && mWidgetsOptionView != null) {
+            // No widgets exist, but we previously added the option so remove it.
+            removeView(mWidgetsOptionView);
+        }
+    }
 }
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
deleted file mode 100644
index afa19b8..0000000
--- a/res/layout/user_folder.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<com.android.launcher3.folder.Folder xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="@drawable/round_rect_primary"
-    android:elevation="5dp"
-    android:orientation="vertical" >
-
-    <com.android.launcher3.folder.FolderPagedView
-        android:id="@+id/folder_content"
-        android:clipToPadding="false"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingLeft="8dp"
-        android:paddingRight="8dp"
-        android:paddingTop="16dp"
-        launcher:pageIndicator="@+id/folder_page_indicator" />
-
-    <LinearLayout
-        android:id="@+id/folder_footer"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:clipChildren="false"
-        android:orientation="horizontal"
-        android:paddingLeft="12dp"
-        android:paddingRight="12dp" >
-
-        <com.android.launcher3.ExtendedEditText
-            android:id="@+id/folder_name"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:layout_weight="1"
-            android:background="@android:color/transparent"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center_horizontal"
-            android:hint="@string/folder_hint_text"
-            android:imeOptions="flagNoExtractUi"
-            android:paddingBottom="@dimen/folder_label_padding_bottom"
-            android:paddingTop="@dimen/folder_label_padding_top"
-            android:singleLine="true"
-            android:textColor="?android:attr/textColorTertiary"
-            android:includeFontPadding="false"
-            android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?android:attr/textColorHint"
-            android:textSize="@dimen/folder_label_text_size" />
-
-        <com.android.launcher3.pageindicators.PageIndicatorDots
-            android:id="@+id/folder_page_indicator"
-            android:layout_gravity="center_vertical"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:elevation="1dp"
-            />
-
-    </LinearLayout>
-
-</com.android.launcher3.folder.Folder>
\ No newline at end of file
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 8c833fa..35d57ff 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbeitsprofil"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Hier findest du Apps für die Arbeit"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"Jede App für die Arbeit hat ein orangefarbenes Logo, um zu zeigen, dass sich deine Organisation um die Sicherheit der App kümmert. Diese Apps können auf deinen Startbildschirm verschoben werden, damit du leichter auf sie zugreifen kannst."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"Wird von deiner Organisation verwaltet"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 14e8e42..31dff6b 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफ़ाइल"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"काम से जुड़े सभी ऐप्लिकेशन यहां पाएं"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"काम से जुड़े हर ऐप्लिकेशन पर एक नारंगी बैज होता है. इसका मतलब यह है कि यह ऐप्लिकेशन आपके संगठन की ओर से सुरक्षित किया जाता है. आसानी से इस्तेमाल के लिए, काम से जुड़े ऐप्लिकेशन होम स्क्रीन पर ले जाए जा सकते हैं."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"आपके संगठन की ओर से प्रबंधित किया गया"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index c632e67..44a872c 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ניתן למצוא כאן את אפליקציות העבודה"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"לכל אפליקציית עבודה יש תג כתום, וזה אומר שאבטחתה מטופלת בידי הארגון. ניתן להעביר אפליקציות עבודה אל מסך דף הבית כדי להקל את הגישה אליהן."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"מנוהל בידי הארגון שלך"</string>
 </resources>
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 9796d18..4d1bedc 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -110,7 +110,7 @@
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
-        if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO
+        if (Utilities.ATLEAST_OREO
                 && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
                 && Process.myUserHandle().equals(lai.getUser())) {
             // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 9775955..469b8bb 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -437,9 +437,11 @@
             }
 
             // Auto installs should always support the current platform version.
+            LauncherIcons li = LauncherIcons.obtain(mContext);
             mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(
-                    LauncherIcons.createBadgedIconBitmap(
-                            icon, Process.myUserHandle(), mContext, VERSION.SDK_INT).icon));
+                    li.createBadgedIconBitmap(icon, Process.myUserHandle(), VERSION.SDK_INT).icon));
+            li.recycle();
+
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index baa60b0..a5ca3ee 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -193,8 +193,10 @@
     }
 
     protected BitmapInfo makeDefaultIcon(UserHandle user) {
-        Drawable unbadged = getFullResDefaultActivityIcon();
-        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, VERSION.SDK_INT);
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            return li.createBadgedIconBitmap(
+                    getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
+        }
     }
 
     /**
@@ -378,8 +380,10 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
-                    mContext,  app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+                    app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+            li.recycle();
         }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
@@ -535,9 +539,10 @@
                 providerFetchedOnce = true;
 
                 if (info != null) {
-                    LauncherIcons.createBadgedIconBitmap(
-                            getFullResIcon(info), info.getUser(), mContext,
+                    LauncherIcons li = LauncherIcons.obtain(mContext);
+                    li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(),
                             info.getApplicationInfo().targetSdkVersion).applyTo(entry);
+                    li.recycle();
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -596,7 +601,9 @@
             entry.title = title;
         }
         if (icon != null) {
-            LauncherIcons.createIconBitmap(icon, mContext).applyTo(entry);
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            li.createIconBitmap(icon).applyTo(entry);
+            li.recycle();
         }
         if (!TextUtils.isEmpty(title) && entry.icon != null) {
             mCache.put(cacheKey, entry);
@@ -633,14 +640,17 @@
                         throw new NameNotFoundException("ApplicationInfo is null");
                     }
 
+                    LauncherIcons li = LauncherIcons.obtain(mContext);
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
-                    BitmapInfo iconInfo = LauncherIcons.createBadgedIconBitmap(
-                            appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
+                    BitmapInfo iconInfo = li.createBadgedIconBitmap(
+                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
                     if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        LauncherIcons.badgeWithDrawable(iconInfo.icon,
-                                mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
+                        li.badgeWithDrawable(iconInfo.icon,
+                                mContext.getDrawable(R.drawable.ic_instant_app_badge));
                     }
+                    li.recycle();
+
                     Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
@@ -776,10 +786,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int DB_VERSION = 18;
-
-        private final static int RELEASE_VERSION = DB_VERSION +
-                (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
+        private final static int RELEASE_VERSION = 20;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index c476421..fe8a841 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -498,7 +498,9 @@
                 return Pair.create((ItemInfo) si, (Object) activityInfo);
             } else if (shortcutInfo != null) {
                 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
-                LauncherIcons.createShortcutIcon(shortcutInfo, mContext).applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(mContext);
+                li.createShortcutIcon(shortcutInfo).applyTo(si);
+                li.recycle();
                 return Pair.create((ItemInfo) si, (Object) shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -643,15 +645,18 @@
         info.user = Process.myUserHandle();
 
         BitmapInfo iconInfo = null;
+        LauncherIcons li = LauncherIcons.obtain(app.getContext());
         if (bitmap instanceof Bitmap) {
-            iconInfo = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+            iconInfo = li.createIconBitmap((Bitmap) bitmap);
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
             if (extra instanceof Intent.ShortcutIconResource) {
                 info.iconResource = (Intent.ShortcutIconResource) extra;
-                iconInfo = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+                iconInfo = li.createIconBitmap(info.iconResource);
             }
         }
+        li.recycle();
+
         if (iconInfo == null) {
             iconInfo = app.getIconCache().getDefaultIcon(info.user);
         }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ea4b280..1b169f5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -620,7 +620,9 @@
             @Override
             public ShortcutInfo get() {
                 si.updateFromDeepShortcutInfo(info, mApp.getContext());
-                LauncherIcons.createShortcutIcon(info, mApp.getContext()).applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+                li.createShortcutIcon(info).applyTo(si);
+                li.recycle();
                 return si;
             }
         });
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index bdfeae1..a658d58 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -469,8 +469,11 @@
         }
         RectF boxRect = drawBoxWithShadow(c, size, size);
 
-        Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow(
-                mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, 0);
+        LauncherIcons li = LauncherIcons.obtain(mContext);
+        Bitmap icon = li.createScaledBitmapWithoutShadow(
+                mutateOnMainThread(info.getFullResIcon(mIconCache)), 0);
+        li.recycle();
+
         Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
 
         boxRect.set(0, 0, iconSize, iconSize);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 5e188e9..a40f8b3 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -578,6 +578,12 @@
         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
     }
 
+    public void onScrollUpEnd() {
+        if (mUsingTabs) {
+            ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)).peekWorkTabIfNecessary();
+        }
+    }
+
     public class AdapterHolder {
         public static final int MAIN = 0;
         public static final int WORK = 1;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 5d1fc8e..d8a0d64 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -216,6 +216,7 @@
         } else if (Float.compare(mProgress, 0f) == 0) {
             mHotseat.setVisibility(View.INVISIBLE);
             mAppsView.setVisibility(View.VISIBLE);
+            mAppsView.onScrollUpEnd();
         } else {
             mAppsView.setVisibility(View.VISIBLE);
             mHotseat.setVisibility(View.VISIBLE);
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 47dc36d..393884e 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.support.annotation.NonNull;
@@ -25,17 +27,27 @@
 import android.widget.Button;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
 public class PersonalWorkSlidingTabStrip extends LinearLayout {
+    private static final int POSITION_PERSONAL = 0;
+    private static final int POSITION_WORK = 1;
+    private static final int PEEK_DURATION = 1000;
+    private static final float PEAK_OFFSET = 0.4f;
+
+    private static final String KEY_SHOWED_PEEK_WORK_TAB = "showed_peek_work_tab";
+
     private final Paint mPersonalTabIndicatorPaint;
     private final Paint mWorkTabIndicatorPaint;
     private final Paint mDividerPaint;
+    private final SharedPreferences mSharedPreferences;
 
     private int mSelectedIndicatorHeight;
     private int mIndicatorLeft = -1;
@@ -64,7 +76,10 @@
 
         mDividerPaint = new Paint();
         mDividerPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorControlHighlight));
-        mDividerPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+        mDividerPaint.setStrokeWidth(
+                getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+
+        mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
     }
 
     public void updateIndicatorPosition(int position, float positionOffset) {
@@ -141,4 +156,26 @@
         boolean isPersonal = mIsRtl ^ firstHalf;
         return isPersonal ? mPersonalTabIndicatorPaint : mWorkTabIndicatorPaint;
     }
+
+    public void peekWorkTabIfNecessary() {
+        if (mSharedPreferences.getBoolean(KEY_SHOWED_PEEK_WORK_TAB, false)) {
+            return;
+        }
+        if (mIndicatorPosition != POSITION_PERSONAL) {
+            return;
+        }
+        peekWorkTab();
+        mSharedPreferences.edit().putBoolean(KEY_SHOWED_PEEK_WORK_TAB, true).apply();
+    }
+
+    private void peekWorkTab() {
+        final boolean isRtl = Utilities.isRtl(getResources());
+        ValueAnimator animator = ValueAnimator.ofFloat(0, isRtl ? 1 - PEAK_OFFSET : PEAK_OFFSET, 0);
+        animator.setDuration(PEEK_DURATION);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        animator.addUpdateListener(
+                animation -> updateIndicatorPosition(mIndicatorPosition,
+                        (float) animation.getAnimatedValue()));
+        animator.start();
+    }
 }
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 02ec9f0..72d49f0 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -38,27 +38,32 @@
     private static final String TAG = "BadgeRenderer";
 
     // The badge sizes are defined as percentages of the app icon size.
-    private static final float SIZE_PERCENTAGE = 0.23f;
+    private static final float SIZE_PERCENTAGE = 0.38f;
+
+    // Extra scale down of the dot
+    private static final float DOT_SCALE = 0.6f;
+
     // Used to expand the width of the badge for each additional digit.
     private static final float OFFSET_PERCENTAGE = 0.02f;
 
-    private final int mSize;
+    private final float mDotCenterOffset;
     private final int mOffset;
     private final float mCircleRadius;
     private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
 
     private final Bitmap mBackgroundWithShadow;
-    private final int mBitmapOffset;
+    private final float mBitmapOffset;
 
     public BadgeRenderer(int iconSizePx) {
-        mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
+        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
         mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
 
+        int size = (int) (DOT_SCALE * mDotCenterOffset);
         ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
-        mBackgroundWithShadow = builder.setupBlurForSize(mSize).createPill(mSize, mSize);
+        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
         mCircleRadius = builder.radius;
 
-        mBitmapOffset = -mBackgroundWithShadow.getHeight() / 2; // Same as width.
+        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
     }
 
     /**
@@ -77,8 +82,8 @@
         }
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         // We draw the badge relative to its center.
-        int badgeCenterX = iconBounds.right - mSize / 2;
-        int badgeCenterY = iconBounds.top + mSize / 2;
+        float badgeCenterX = iconBounds.right - mDotCenterOffset / 2;
+        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
 
         int offsetX = Math.min(mOffset, spaceForOffset.x);
         int offsetY = Math.min(mOffset, spaceForOffset.y);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 5cd90b1..173d0d1 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -137,7 +137,9 @@
             ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
             ShortcutInfo info = new ShortcutInfo(compat, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
-            LauncherIcons.createShortcutIcon(compat, context, false /* badged */).applyTo(info);
+            LauncherIcons li = LauncherIcons.obtain(context);
+            li.createShortcutIcon(compat, false /* badged */).applyTo(info);
+            li.recycle();
             LauncherAppState.getInstance(context).getModel()
                     .updateAndBindShortcutInfo(info, compat);
             return info;
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 0d92d45..18797a4 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -31,8 +31,6 @@
     public static final boolean IS_DOGFOOD_BUILD = false;
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
 
-    // Custom flags go below this
-    public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
     // When enabled allows to use any point on the fast scrollbar to start dragging.
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
     // When enabled the promise icon is visible in all apps while installation an app.
@@ -46,10 +44,6 @@
     public static final boolean QSB_ON_FIRST_SCREEN = true;
     // When enabled the all-apps icon is not added to the hotseat.
     public static final boolean NO_ALL_APPS_ICON = true;
-    // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
-    public static final boolean LEGACY_ICON_TREATMENT = true;
-    // When enabled, adaptive icons would have shadows baked when being stored to icon cache.
-    public static final boolean ADAPTIVE_ICON_SHADOW = true;
     // When enabled, app discovery will be enabled if service is implemented
     public static final boolean DISCOVERY_ENABLED = false;
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 11ff88f..9732261 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,8 +224,10 @@
                     mBadge = getBadge(info, appState, outObj[0]);
                     mBadge.setBounds(badgeBounds);
 
+                    LauncherIcons li = LauncherIcons.obtain(mLauncher);
                     Utilities.scaleRectAboutCenter(bounds,
-                            IconNormalizer.getInstance(mLauncher).getScale(dr, null, null, null));
+                            li.getNormalizer().getScale(dr, null, null, null));
+                    li.recycle();
                     AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
 
                     // Shrink very tiny bit so that the clip path is smaller than the original bitmap
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d4c396a..64f96d5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -453,9 +453,8 @@
      */
     @SuppressLint("InflateParams")
     static Folder fromXml(Launcher launcher) {
-        return (Folder) launcher.getLayoutInflater().inflate(
-                FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION
-                        ? R.layout.user_folder : R.layout.user_folder_icon_normalized, null);
+        return (Folder) launcher.getLayoutInflater()
+                .inflate(R.layout.user_folder_icon_normalized, null);
     }
 
     private void startAnimation(final AnimatorSet a) {
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 5ee6a30..bd20c87 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -37,10 +37,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
-import java.util.Random;
 
 public class IconNormalizer {
 
@@ -64,9 +61,6 @@
     private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
     private static final float SCALE_NOT_INITIALIZED = 0;
 
-    private static final Object LOCK = new Object();
-    private static IconNormalizer sIconNormalizer;
-
     private final int mMaxSize;
     private final Bitmap mBitmap;
     private final Bitmap mBitmapARGB;
@@ -88,11 +82,8 @@
     private final Paint mPaintIcon;
     private final Canvas mCanvasARGB;
 
-    private final File mDir;
-    private int mFileId;
-    private final Random mRandom;
-
-    private IconNormalizer(Context context) {
+    /** package private **/
+    IconNormalizer(Context context) {
         // Use twice the icon size as maximum size to avoid scaling down twice.
         mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
         mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -124,9 +115,6 @@
 
         mMatrix = new Matrix();
         mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
-
-        mDir = context.getExternalFilesDir(null);
-        mRandom = new Random();
     }
 
     /**
@@ -148,18 +136,9 @@
         // Condition 2:
         // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
         // should generate transparent image, if the actual icon is equivalent to the shape.
-        mFileId = mRandom.nextInt();
         mBitmapARGB.eraseColor(Color.TRANSPARENT);
         mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);
 
-        if (DEBUG) {
-            final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
-            try {
-                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
-                        new FileOutputStream(beforeFile));
-            } catch (Exception e) {}
-        }
-
         // Fit the shape within the icon's bounding box
         mMatrix.reset();
         mMatrix.setScale(mBounds.width(), mBounds.height());
@@ -172,24 +151,8 @@
         // DST_OUT operation around the mask path outline
         mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
 
-        boolean isTrans = isTransparentBitmap(mBitmapARGB);
-        if (DEBUG) {
-            final File afterFile = new File(mDir,
-                    "isShape" + mFileId + "_after_" + isTrans + ".png");
-            try {
-                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
-                        new FileOutputStream(afterFile));
-            } catch (Exception e) {}
-        }
-
         // Check if the result is almost transparent
-        if (!isTrans) {
-            if (DEBUG) {
-                Log.d(TAG, "Not same as mask shape");
-            }
-            return false;
-        }
-        return true;
+        return isTransparentBitmap(mBitmapARGB);
     }
 
     /**
@@ -203,19 +166,13 @@
                 mBounds.left, mBounds.top,
                 w, h);
         int sum = 0;
-        for (int i = 0; i < w * h; i++) {
+        for (int i = w * h - 1; i >= 0; i--) {
             if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
                     sum++;
             }
         }
         float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
-        boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
-        if (DEBUG) {
-            Log.d(TAG,
-                    "Total # pixel that is different (id=" + mFileId + "):" + percentageDiffPixels
-                            + "=" + sum + "/" + mBounds.width() * mBounds.height());
-        }
-        return transparentImage;
+        return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
     }
 
     /**
@@ -416,13 +373,4 @@
             last = i;
         }
     }
-
-    public static IconNormalizer getInstance(Context context) {
-        synchronized (LOCK) {
-            if (sIconNormalizer == null) {
-                sIconNormalizer = new IconNormalizer(context);
-            }
-        }
-        return sIconNormalizer;
-    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index fdb6313..0c9f4d9 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -16,7 +16,11 @@
 
 package com.android.launcher3.graphics;
 
-import android.annotation.TargetApi;
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,7 +29,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -41,11 +44,11 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.IconCache;
+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.config.FeatureFlags;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -56,32 +59,95 @@
 /**
  * Helper methods for generating various launcher icons
  */
-public class LauncherIcons {
+public class LauncherIcons implements AutoCloseable {
 
-    private static final Rect sOldBounds = new Rect();
-    private static final Canvas sCanvas = new Canvas();
+    public static final Object sPoolSync = new Object();
+    private static LauncherIcons sPool;
 
-    static {
-        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
-                Paint.FILTER_BITMAP_FLAG));
+    /**
+     * Return a new Message instance from the global pool. Allows us to
+     * avoid allocating new objects in many cases.
+     */
+    public static LauncherIcons obtain(Context context) {
+        synchronized (sPoolSync) {
+            if (sPool != null) {
+                LauncherIcons m = sPool;
+                sPool = m.next;
+                m.next = null;
+                return m;
+            }
+        }
+        return new LauncherIcons(context);
+    }
+
+    /**
+     * Recycles a LauncherIcons that may be in-use.
+     */
+    public void recycle() {
+        synchronized (sPoolSync) {
+            next = sPool;
+            sPool = this;
+        }
+    }
+
+    @Override
+    public void close() {
+        recycle();
+    }
+
+    private final Rect mOldBounds = new Rect();
+    private final Context mContext;
+    private final Canvas mCanvas;
+    private final PackageManager mPm;
+
+    private final int mFillResIconDpi;
+    private final int mIconBitmapSize;
+
+    private IconNormalizer mNormalizer;
+    private ShadowGenerator mShadowGenerator;
+
+    // sometimes we store linked lists of these things
+    private LauncherIcons next;
+
+    private LauncherIcons(Context context) {
+        mContext = context.getApplicationContext();
+        mPm = mContext.getPackageManager();
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        mFillResIconDpi = idp.fillResIconDpi;
+        mIconBitmapSize = idp.iconBitmapSize;
+
+        mCanvas = new Canvas();
+        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+    }
+
+    public ShadowGenerator getShadowGenerator() {
+        if (mShadowGenerator == null) {
+            mShadowGenerator = new ShadowGenerator(mContext);
+        }
+        return mShadowGenerator;
+    }
+
+    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 static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) {
-        PackageManager packageManager = context.getPackageManager();
-        // the resource
+    public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
         try {
-            Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
+            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, LauncherAppState.getIDP(context).fillResIconDpi),
+                return createBadgedIconBitmap(
+                        resources.getDrawableForDensity(id, mFillResIconDpi),
                         Process.myUserHandle() /* only available on primary user */,
-                        context,
                         0 /* do not apply legacy treatment */);
             }
         } catch (Exception e) {
@@ -93,13 +159,12 @@
     /**
      * Returns a bitmap which is of the appropriate size to be displayed as an icon
      */
-    public static BitmapInfo createIconBitmap(Bitmap icon, Context context) {
-        final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
-        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+    public BitmapInfo createIconBitmap(Bitmap icon) {
+        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
             return BitmapInfo.fromBitmap(icon);
         }
         return BitmapInfo.fromBitmap(
-                createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f));
+                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
     }
 
     /**
@@ -107,51 +172,24 @@
      * view or workspace. The icon is badged for {@param user}.
      * The bitmap is also visually normalized with other icons.
      */
-    public static BitmapInfo createBadgedIconBitmap(
-            Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
-
-        IconNormalizer normalizer;
-        float scale = 1f;
-        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
-            normalizer = IconNormalizer.getInstance(context);
-            if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
-                boolean[] outShape = new boolean[1];
-                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                dr.setBounds(0, 0, 1, 1);
-                scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
-                if (FeatureFlags.LEGACY_ICON_TREATMENT &&
-                        !outShape[0]){
-                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
-                    if (wrappedIcon != icon) {
-                        icon = wrappedIcon;
-                        scale = normalizer.getScale(icon, null, null, null);
-                    }
-                }
-            } else {
-                scale = normalizer.getScale(icon, null, null, null);
-            }
-        }
-        Bitmap bitmap = createIconBitmap(icon, context, scale);
-        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
-                icon instanceof AdaptiveIconDrawable) {
-            synchronized (sCanvas) {
-                sCanvas.setBitmap(bitmap);
-                ShadowGenerator.getInstance(context).recreateIcon(
-                        Bitmap.createBitmap(bitmap), sCanvas);
-                sCanvas.setBitmap(null);
-            }
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
+        float[] 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 = context.getPackageManager().getUserBadgedIcon(
-                    drawable, user);
+            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
             if (badged instanceof BitmapDrawable) {
                 result = ((BitmapDrawable) badged).getBitmap();
             } else {
-                result = createIconBitmap(badged, context, 1f);
+                result = createIconBitmap(badged, 1f);
             }
         } else {
             result = bitmap;
@@ -163,170 +201,134 @@
      * 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 static Bitmap createScaledBitmapWithoutShadow(
-            Drawable icon, Context context, int iconAppTargetSdk) {
+    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
         RectF iconBounds = new RectF();
-        IconNormalizer normalizer;
+        float[] scale = new float[1];
+        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
+        return createIconBitmap(icon,
+                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+    }
+
+    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
+            RectF outIconBounds, float[] outScale) {
         float scale = 1f;
-        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
-            normalizer = IconNormalizer.getInstance(context);
-            if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
-                boolean[] outShape = new boolean[1];
-                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                dr.setBounds(0, 0, 1, 1);
-                scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
-                if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
-                        !outShape[0]) {
-                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
-                    if (wrappedIcon != icon) {
-                        icon = wrappedIcon;
-                        scale = normalizer.getScale(icon, iconBounds, null, null);
-                    }
-                }
-            } else {
-                scale = normalizer.getScale(icon, iconBounds, null, null);
+        if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+            boolean[] outShape = new boolean[1];
+            AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+                    mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+            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);
             }
-
+        } else {
+            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
         }
-        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
-        return createIconBitmap(icon, context, scale);
+
+        outScale[0] = scale;
+        return icon;
     }
 
     /**
      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
      */
-    public static void badgeWithDrawable(Bitmap target, Drawable badge, Context context) {
-        synchronized (sCanvas) {
-            sCanvas.setBitmap(target);
-            badgeWithDrawable(sCanvas, badge, context);
-            sCanvas.setBitmap(null);
-        }
+    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 static void badgeWithDrawable(Canvas target, Drawable badge, Context context) {
-        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
-        int iconSize = LauncherAppState.getIDP(context).iconBitmapSize;
-        badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
+    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 static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
-        synchronized (sCanvas) {
-            final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
-            int width = iconBitmapSize;
-            int height = iconBitmapSize;
+    private Bitmap createIconBitmap(Drawable icon, float scale) {
+        int width = mIconBitmapSize;
+        int height = mIconBitmapSize;
 
-            if (icon instanceof PaintDrawable) {
-                PaintDrawable painter = (PaintDrawable) icon;
-                painter.setIntrinsicWidth(width);
-                painter.setIntrinsicHeight(height);
-            } else if (icon instanceof BitmapDrawable) {
-                // Ensure the bitmap has a density.
-                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-                Bitmap bitmap = bitmapDrawable.getBitmap();
-                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
-                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
-                }
+        if (icon instanceof PaintDrawable) {
+            PaintDrawable painter = (PaintDrawable) icon;
+            painter.setIntrinsicWidth(width);
+            painter.setIntrinsicHeight(height);
+        } else if (icon instanceof BitmapDrawable) {
+            // Ensure the bitmap has a density.
+            BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+            Bitmap bitmap = bitmapDrawable.getBitmap();
+            if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+                bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
             }
-
-            int sourceWidth = icon.getIntrinsicWidth();
-            int sourceHeight = icon.getIntrinsicHeight();
-            if (sourceWidth > 0 && sourceHeight > 0) {
-                // Scale the icon proportionally to the icon dimensions
-                final float ratio = (float) sourceWidth / sourceHeight;
-                if (sourceWidth > sourceHeight) {
-                    height = (int) (width / ratio);
-                } else if (sourceHeight > sourceWidth) {
-                    width = (int) (height * ratio);
-                }
-            }
-            // no intrinsic size --> use default size
-            int textureWidth = iconBitmapSize;
-            int textureHeight = iconBitmapSize;
-
-            Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
-                    Bitmap.Config.ARGB_8888);
-            final Canvas canvas = sCanvas;
-            canvas.setBitmap(bitmap);
-
-            final int left = (textureWidth-width) / 2;
-            final int top = (textureHeight-height) / 2;
-
-            sOldBounds.set(icon.getBounds());
-            if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
-                int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
-                        Math.min(left, top));
-                int size = Math.max(width, height);
-                icon.setBounds(offset, offset, size, size);
-            } else {
-                icon.setBounds(left, top, left+width, top+height);
-            }
-            canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
-            icon.draw(canvas);
-            canvas.restore();
-            icon.setBounds(sOldBounds);
-            canvas.setBitmap(null);
-
-            return bitmap;
-        }
-    }
-
-    /**
-     * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
-     * shrink the legacy icon and set it as foreground. Use color drawable as background to
-     * create AdaptiveIconDrawable.
-     */
-    @TargetApi(Build.VERSION_CODES.O)
-    private static Drawable wrapToAdaptiveIconDrawable(
-            Context context, Drawable drawable, float scale) {
-        if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
-            return drawable;
         }
 
-        try {
-            if (!(drawable instanceof AdaptiveIconDrawable)) {
-                AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
-                fsd.setDrawable(drawable);
-                fsd.setScale(scale);
-                return iconWrapper;
+        int sourceWidth = icon.getIntrinsicWidth();
+        int sourceHeight = icon.getIntrinsicHeight();
+        if (sourceWidth > 0 && sourceHeight > 0) {
+            // Scale the icon proportionally to the icon dimensions
+            final float ratio = (float) sourceWidth / sourceHeight;
+            if (sourceWidth > sourceHeight) {
+                height = (int) (width / ratio);
+            } else if (sourceHeight > sourceWidth) {
+                width = (int) (height * ratio);
             }
-        } catch (Exception e) {
-            return drawable;
         }
-        return drawable;
+        // no intrinsic size --> use default size
+        int textureWidth = mIconBitmapSize;
+        int textureHeight = mIconBitmapSize;
+
+        Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+                Bitmap.Config.ARGB_8888);
+        mCanvas.setBitmap(bitmap);
+
+        final int left = (textureWidth-width) / 2;
+        final int top = (textureHeight-height) / 2;
+
+        mOldBounds.set(icon.getBounds());
+        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            int offset = Math.max((int)(BLUR_FACTOR * textureWidth), Math.min(left, top));
+            int size = Math.max(width, height);
+            icon.setBounds(offset, offset, size, size);
+        } else {
+            icon.setBounds(left, top, left+width, top+height);
+        }
+        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
+        mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+        icon.draw(mCanvas);
+        mCanvas.restore();
+        icon.setBounds(mOldBounds);
+        mCanvas.setBitmap(null);
+
+        return bitmap;
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
-        return createShortcutIcon(shortcutInfo, context, true /* badged */);
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
+        return createShortcutIcon(shortcutInfo, true /* badged */);
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
-            boolean badged) {
-        return createShortcutIcon(shortcutInfo, context, badged, null);
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) {
+        return createShortcutIcon(shortcutInfo, badged, null);
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
             boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
-        LauncherAppState app = LauncherAppState.getInstance(context);
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
-                .getShortcutIconDrawable(shortcutInfo,
-                        app.getInvariantDeviceProfile().fillResIconDpi);
-        IconCache cache = app.getIconCache();
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
+                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+        IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
 
         Bitmap unbadgedBitmap = null;
         if (unbadgedDrawable != null) {
-            unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
-                    unbadgedDrawable, context, 0);
+            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
         } else {
             if (fallbackIconProvider != null) {
                 unbadgedBitmap = fallbackIconProvider.get();
@@ -338,20 +340,18 @@
 
         BitmapInfo result = new BitmapInfo();
         if (!badged) {
-            result.color = Themes.getColorAccent(context);
+            result.color = Themes.getColorAccent(mContext);
             result.icon = unbadgedBitmap;
             return result;
         }
 
-        int size = app.getInvariantDeviceProfile().iconBitmapSize;
-
         final Bitmap unbadgedfinal = unbadgedBitmap;
         final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
 
         result.color = badge.iconColor;
-        result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> {
-            ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
-            badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
+        result.icon = UiFactory.createFromRenderer(mIconBitmapSize, mIconBitmapSize, false, (c) -> {
+            getShadowGenerator().recreateIcon(unbadgedfinal, c);
+            badgeWithDrawable(c, new FastBitmapDrawable(badge));
         });
         return result;
     }
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 96f2629..5fbf502 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -46,17 +46,13 @@
 
     private static final int AMBIENT_SHADOW_ALPHA = 30;
 
-    private static final Object LOCK = new Object();
-    // Singleton object guarded by {@link #LOCK}
-    private static ShadowGenerator sShadowGenerator;
-
     private final int mIconSize;
 
     private final Paint mBlurPaint;
     private final Paint mDrawPaint;
     private final BlurMaskFilter mDefaultBlurMaskFilter;
 
-    private ShadowGenerator(Context context) {
+    public ShadowGenerator(Context context) {
         mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
         mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -86,18 +82,6 @@
         out.drawBitmap(icon, 0, 0, mDrawPaint);
     }
 
-    public static ShadowGenerator getInstance(Context context) {
-        // TODO: This currently fails as the system default icon also needs a shadow as it
-        // uses adaptive icon.
-        // Preconditions.assertNonUiThread();
-        synchronized (LOCK) {
-            if (sShadowGenerator == null) {
-                sShadowGenerator = new ShadowGenerator(context);
-            }
-        }
-        return sShadowGenerator;
-    }
-
     /**
      * Returns the minimum amount by which an icon with {@param bounds} should be scaled
      * so that the shadows do not get clipped.
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index b1d07f1..6378ea1 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -173,7 +173,9 @@
                 info.iconResource = new ShortcutIconResource();
                 info.iconResource.packageName = packageName;
                 info.iconResource.resourceName = resourceName;
-                BitmapInfo iconInfo = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+                LauncherIcons li = LauncherIcons.obtain(mContext);
+                BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+                li.recycle();
                 if (iconInfo != null) {
                     iconInfo.applyTo(info);
                     return true;
@@ -183,9 +185,8 @@
 
         // Failed to load from resource, try loading from DB.
         byte[] data = getBlob(iconIndex);
-        try {
-            LauncherIcons.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length),
-                    mContext).applyTo(info);
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
             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 b13b48a..883c33d 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -215,9 +215,10 @@
 
     public void loadUiResources() {
         if (Utilities.ATLEAST_OREO) {
-            ClickShadowView.setAdaptiveIconScaleFactor(
-                    IconNormalizer.getInstance(mApp.getContext()).getScale(
-                            new AdaptiveIconDrawable(null, null), null, null, null));
+            LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+            ClickShadowView.setAdaptiveIconScaleFactor(li.getNormalizer()
+                    .getScale(new AdaptiveIconDrawable(null, null), null, null, null));
+            li.recycle();
         }
     }
 
@@ -476,8 +477,10 @@
                                                     ? finalInfo.iconBitmap : null;
                                         }
                                     };
-                                    LauncherIcons.createShortcutIcon(pinnedShortcut, context,
+                                    LauncherIcons li = LauncherIcons.obtain(context);
+                                    li.createShortcutIcon(pinnedShortcut,
                                             true /* badged */, fallbackIconProvider).applyTo(info);
+                                    li.recycle();
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 18ae61b..089303e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -192,8 +192,9 @@
                         // Update shortcuts which use iconResource.
                         if ((si.iconResource != null)
                                 && packageSet.contains(si.iconResource.packageName)) {
-                            BitmapInfo iconInfo =
-                                    LauncherIcons.createIconBitmap(si.iconResource, context);
+                            LauncherIcons li = LauncherIcons.obtain(context);
+                            BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
+                            li.recycle();
                             if (iconInfo != null) {
                                 iconInfo.applyTo(si);
                                 infoUpdated = true;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 0b75e2c..59f3d1c 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -95,8 +95,10 @@
                     shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
                     // 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.createShortcutIcon(fullDetails, context, true,
-                            Provider.of(shortcutInfo.iconBitmap)).applyTo(shortcutInfo);
+                    LauncherIcons li = LauncherIcons.obtain(context);
+                    li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
+                            .applyTo(shortcutInfo);
+                    li.recycle();
                     updatedShortcutInfos.add(shortcutInfo);
                 }
             }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index b033405..9521a9e 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -94,8 +94,9 @@
                     si.updateFromDeepShortcutInfo(shortcut, context);
                     // 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.createShortcutIcon(shortcut, context, true,
-                            Provider.of(si.iconBitmap)).applyTo(si);
+                    LauncherIcons li = LauncherIcons.obtain(context);
+                    li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
+                    li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 4adfb7c..b295bb2 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -148,8 +148,9 @@
                 final ShortcutInfoCompat shortcut = shortcuts.get(i);
                 final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
                 // Use unbadged icon for the menu.
-                LauncherIcons.createShortcutIcon(shortcut, launcher, false /* badged */)
-                        .applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(launcher);
+                li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
+                li.recycle();
                 si.rank = i;
 
                 final DeepShortcutView view = shortcutViews.get(i);
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index c5cf5e2..aa5b785 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -113,7 +113,9 @@
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
-            preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, 0);
+            LauncherIcons li = LauncherIcons.obtain(launcher);
+            preview = li.createScaledBitmapWithoutShadow(icon, 0);
+            li.recycle();
             scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
 
             dragOffset = new Point(previewPadding / 2, previewPadding / 2);