Merge "Moving divider line into predictionview." into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 20cda1c..435d57e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -367,7 +367,7 @@
         mCurrentAnimation.setEndAction(() -> {
             // TODO: Add logging
             clearState();
-            mLauncher.getStateManager().goToState(OVERVIEW, false /* animated */);
+            mLauncher.getStateManager().goToState(OVERVIEW, true /* animated */);
         });
 
         if (mTwoStateAnimationController != null) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 9178d8a..9be0d32 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.uioverrides;
 
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.view.View.AccessibilityDelegate;
 import android.widget.PopupMenu;
 import android.widget.Toast;
@@ -24,11 +26,16 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.systemui.shared.recents.view.RecentsTransition;
 
 public class UiFactory {
 
+    public static final boolean USE_HARDWARE_BITMAP = FeatureFlags.IS_DOGFOOD_BUILD;
+
     public static TouchController[] createTouchControllers(Launcher launcher) {
 
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -77,4 +84,15 @@
         }
         menu.show();
     }
+
+    public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
+            BitmapRenderer renderer) {
+        if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
+            return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
+        } else {
+            Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            renderer.render(new Canvas(result));
+            return result;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 965696f..43a01a7 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -199,9 +199,9 @@
     private void executeFrameUpdate() {
         if (mLauncherReady) {
             final float displacement = -mCurrentDisplacement;
-            int hotseatHeight = mHotseat.getHeight();
-            float translation = Utilities.boundToRange(displacement, 0, hotseatHeight);
-            float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
+            int hotseatSize = getHotseatSize();
+            float translation = Utilities.boundToRange(displacement, 0, hotseatSize);
+            float shift = hotseatSize == 0 ? 0 : translation / hotseatSize;
             mCurrentShift.updateValue(shift);
         }
     }
@@ -215,13 +215,14 @@
         if (mTargetRect.isEmpty()) {
             RecentsView.getPageRect(mLauncher, mTargetRect);
             DragLayer dl = mLauncher.getDragLayer();
-            mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
+            mSourceRect.set(0, 0, dl.getWidth() - mStableInsets.left - mStableInsets.right,
+                    dl.getHeight() - mStableInsets.top - mStableInsets.bottom);
         }
 
         float shift = mCurrentShift.value * mActivityMultiplier.value;
-        int hotseatHeight = mHotseat.getHeight();
+        int hotseatSize = getHotseatSize();
 
-        mHotseat.setTranslationY((1 - shift) * hotseatHeight);
+        mHotseat.setTranslationY((1 - shift) * hotseatSize);
 
         mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
 
@@ -230,10 +231,17 @@
         mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
         mDragView.setScaleX(scale);
         mDragView.setScaleY(scale);
+        //  TODO: mDragView.getViewBounds().setClipLeft((int) (mStableInsets.left * shift));
         mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
+        // TODO: mDragView.getViewBounds().setClipRight((int) (mStableInsets.right * shift));
         mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
     }
 
+    private int getHotseatSize() {
+        return mLauncher.getDeviceProfile().isVerticalBarLayout()
+                ? mHotseat.getWidth() : mHotseat.getHeight();
+    }
+
     @UiThread
     public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
         mLoadPlan = loadPlan;
@@ -272,7 +280,7 @@
             endShift = endVelocity < 0 ? 1 : 0;
             float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
-                float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
+                float distanceToTravel = (endShift - mCurrentShift.value) * getHotseatSize();
 
                 // we want the page's snap velocity to approximately match the velocity at
                 // which the user flings, so we scale the duration by a value near to the
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 6e8bbeb..4a9bfea 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -25,7 +25,6 @@
 import android.graphics.LightingColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.util.AttributeSet;
@@ -154,17 +153,15 @@
                 final Configuration configuration =
                         getContext().getApplicationContext().getResources().getConfiguration();
                 final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
-                if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
-                    if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
-                        // If we are in the same orientation as the screenshot, just scale it to the
-                        // width of the task view
-                        mThumbnailScale = (float) getMeasuredWidth() / mThumbnailRect.width();
-                    } else {
-                        // Scale the landscape thumbnail up to app size, then scale that to the task
-                        // view size to match other portrait screenshots
-                        mThumbnailScale = invThumbnailScale *
-                                ((float) getMeasuredWidth() / profile.getCurrentWidth());
-                    }
+                if (configuration.orientation == mThumbnailData.orientation) {
+                    // If we are in the same orientation as the screenshot, just scale it to the
+                    // width of the task view
+                    mThumbnailScale = (float) getMeasuredWidth() / mThumbnailRect.width();
+                } else if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+                    // Scale the landscape thumbnail up to app size, then scale that to the task
+                    // view size to match other portrait screenshots
+                    mThumbnailScale = invThumbnailScale *
+                            ((float) getMeasuredWidth() / profile.getCurrentWidth());
                 } else {
                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
                     mThumbnailScale = invThumbnailScale;
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7b220d8..f457a59 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -44,6 +44,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -61,6 +62,7 @@
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.function.Consumer;
 
@@ -105,6 +107,8 @@
     private int mTouchSlop;
     private float mStartDisplacement;
     private NavBarSwipeInteractionHandler mInteractionHandler;
+    private int mDisplayRotation;
+    private Rect mStableInsets = new Rect();
 
     private ISystemUiProxy mISystemUiProxy;
     private Consumer<MotionEvent> mCurrentConsumer = mNoOpTouchConsumer;
@@ -181,6 +185,10 @@
                     mInteractionHandler.endTouch(0);
                     mInteractionHandler = null;
                 }
+
+                Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+                mDisplayRotation = display.getRotation();
+                WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
                 break;
             }
             case ACTION_POINTER_UP: {
@@ -206,6 +214,11 @@
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
 
                 float displacement = ev.getY(pointerIndex) - mDownPos.y;
+                if (isNavBarOnRight()) {
+                    displacement = ev.getX(pointerIndex) - mDownPos.x;
+                } else if (isNavBarOnLeft()) {
+                    displacement = mDownPos.x - ev.getX(pointerIndex);
+                }
                 if (mInteractionHandler == null) {
                     if (Math.abs(displacement) >= mTouchSlop) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
@@ -229,6 +242,13 @@
         }
     }
 
+    private boolean isNavBarOnRight() {
+        return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
+    }
+
+    private boolean isNavBarOnLeft() {
+        return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
+    }
 
     private void startTouchTracking() {
         // Create the shared handler
@@ -272,7 +292,10 @@
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
 
-            mInteractionHandler.endTouch(mVelocityTracker.getYVelocity(mActivePointerId));
+            float velocity = isNavBarOnRight() ? mVelocityTracker.getXVelocity(mActivePointerId)
+                    : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
+                    : mVelocityTracker.getYVelocity(mActivePointerId);
+            mInteractionHandler.endTouch(velocity);
             mInteractionHandler = null;
         }
         mVelocityTracker.recycle();
@@ -290,9 +313,16 @@
         Point displaySize = new Point();
         Display display = getSystemService(WindowManager.class).getDefaultDisplay();
         display.getRealSize(displaySize);
+        int rotation = display.getRotation();
+        // The rotation is backwards in landscape, so flip it.
+        if (rotation == Surface.ROTATION_270) {
+            rotation = Surface.ROTATION_90;
+        } else if (rotation == Surface.ROTATION_90) {
+            rotation = Surface.ROTATION_270;
+        }
         try {
             return mISystemUiProxy.screenshot(new Rect(), displaySize.x, displaySize.y, 0, 100000,
-                    false, display.getRotation()).toBitmap();
+                    false, rotation).toBitmap();
         } catch (RemoteException e) {
             Log.e(TAG, "Error capturing snapshot", e);
             return null;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 573e8a2..ab853e5 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -41,17 +41,20 @@
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
+
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
 import com.android.launcher3.util.Thunk;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -102,6 +105,7 @@
     @Thunk final Handler mWorkerHandler;
 
     private final BitmapFactory.Options mLowResOptions;
+    private final BitmapFactory.Options mHighResOptions;
 
     public IconCache(Context context, InvariantDeviceProfile inv) {
         mContext = context;
@@ -120,6 +124,13 @@
         // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
         // automatically be loaded as ALPHA_8888.
         mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+
+        if (UiFactory.USE_HARDWARE_BITMAP) {
+            mHighResOptions = new BitmapFactory.Options();
+            mHighResOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
+        } else {
+            mHighResOptions = null;
+        }
     }
 
     private Drawable getFullResDefaultActivityIcon() {
@@ -625,7 +636,7 @@
                     Bitmap icon = LauncherIcons.createBadgedIconBitmap(
                             appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
                     if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        icon = LauncherIcons.badgeWithDrawable(icon,
+                        LauncherIcons.badgeWithDrawable(icon,
                                 mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
                     }
                     Bitmap lowResIcon =  generateLowResIcon(icon);
@@ -665,7 +676,7 @@
                 new String[]{cacheKey.componentName.flattenToString(),
                         Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
             if (c.moveToNext()) {
-                entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
+                entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : mHighResOptions);
                 entry.isLowResIcon = lowRes;
                 entry.title = c.getString(1);
                 if (entry.title == null) {
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index c905460..1c6f77c 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -36,10 +36,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Preconditions;
 
-import java.util.concurrent.Callable;
-
 /**
  * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
  */
@@ -66,7 +65,7 @@
     }
 
     public static FolderAdaptiveIcon createFolderAdaptiveIcon(
-            final Launcher launcher, final long folderId, Point dragViewSize) {
+            Launcher launcher, long folderId, Point dragViewSize) {
         Preconditions.assertNonUiThread();
         int margin = launcher.getResources()
                 .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
@@ -75,21 +74,12 @@
         final Bitmap badge = Bitmap.createBitmap(
                 dragViewSize.x - margin, dragViewSize.y - margin, Bitmap.Config.ARGB_8888);
 
-        // The bitmap for the preview is generated larger than needed to allow for the spring effect
-        float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
-        final Bitmap preview = Bitmap.createBitmap(
-                (int) (dragViewSize.x * sizeScaleFactor), (int) (dragViewSize.y * sizeScaleFactor),
-                Bitmap.Config.ARGB_8888);
-
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
-            return new MainThreadExecutor().submit(new Callable<FolderAdaptiveIcon>() {
-                @Override
-                public FolderAdaptiveIcon call() throws Exception {
-                    FolderIcon icon = launcher.findFolderIcon(folderId);
-                    return icon == null ? null : createDrawableOnUiThread(icon, badge, preview);
-                }
+            return new MainThreadExecutor().submit(() -> {
+                FolderIcon icon = launcher.findFolderIcon(folderId);
+                return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
             }).get();
         } catch (Exception e) {
             Log.e(TAG, "Unable to create folder icon", e);
@@ -101,7 +91,7 @@
      * Initializes various bitmaps on the UI thread and returns the final drawable.
      */
     private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
-            Bitmap badgeBitmap, Bitmap previewBitmap) {
+            Bitmap badgeBitmap, Point dragViewSize) {
         Preconditions.assertUIThread();
         float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
 
@@ -115,15 +105,21 @@
         icon.drawBadge(c);
 
         // Initialize preview
-        float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() /
-                (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
-        float previewShiftX = shiftFactor * previewBitmap.getWidth();
-        float previewShiftY = shiftFactor * previewBitmap.getHeight();
+        final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
+        final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
+        final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
 
-        c.setBitmap(previewBitmap);
-        c.translate(previewShiftX, previewShiftY);
-        icon.getPreviewItemManager().draw(c);
-        c.setBitmap(null);
+        final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
+        final float previewShiftX = shiftFactor * previewWidth;
+        final float previewShiftY = shiftFactor * previewHeight;
+
+        Bitmap previewBitmap = UiFactory.createFromRenderer(previewWidth, previewHeight, false,
+                (canvas) -> {
+                    int count = canvas.save();
+                    canvas.translate(previewShiftX, previewShiftY);
+                    icon.getPreviewItemManager().draw(canvas);
+                    canvas.restoreToCount(count);
+                });
 
         // Initialize mask
         Path mask = new Path();
diff --git a/src/com/android/launcher3/graphics/BitmapRenderer.java b/src/com/android/launcher3/graphics/BitmapRenderer.java
new file mode 100644
index 0000000..4652ded
--- /dev/null
+++ b/src/com/android/launcher3/graphics/BitmapRenderer.java
@@ -0,0 +1,23 @@
+/*
+ * 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.graphics;
+
+import android.graphics.Canvas;
+
+public interface BitmapRenderer {
+
+     void render(Canvas out);
+}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 355c231..902906f 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.UiThreadHelper;
 
 import java.nio.ByteBuffer;
@@ -77,8 +78,10 @@
     /**
      * Draws the {@link #mView} into the given {@param destCanvas}.
      */
-    private void drawDragView(Canvas destCanvas) {
+    private void drawDragView(Canvas destCanvas, float scale) {
         destCanvas.save();
+        destCanvas.scale(scale, scale);
+
         if (mView instanceof BubbleTextView) {
             Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
@@ -120,6 +123,7 @@
         int width = mView.getWidth();
         int height = mView.getHeight();
 
+        boolean forceSoftwareRenderer = false;
         if (mView instanceof BubbleTextView) {
             Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
@@ -129,20 +133,14 @@
             scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
             width = (int) (mView.getWidth() * scale);
             height = (int) (mView.getHeight() * scale);
+
+            // Use software renderer for widgets as we know that they already work
+            forceSoftwareRenderer = true;
         }
 
-        Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
-                Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(b);
-
-        canvas.save();
-        canvas.scale(scale, scale);
-        drawDragView(canvas);
-        canvas.restore();
-
-        canvas.setBitmap(null);
-
-        return b;
+        final float scaleFinal = scale;
+        return UiFactory.createFromRenderer(width + blurSizeOutline, height + blurSizeOutline,
+                forceSoftwareRenderer, (c) -> drawDragView(c, scaleFinal));
     }
 
     public final void generateDragOutline(Bitmap preview) {
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index fdf2d67..ebfe1e7 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Utility class to generate shadow and outline effect, which are used for click feedback
@@ -106,7 +107,11 @@
         int saveCount = mCanvas.save();
         mCanvas.scale(scaleX, scaleY);
         mCanvas.translate(-rect.left, -rect.top);
-        drawable.draw(mCanvas);
+        if (!UiFactory.USE_HARDWARE_BITMAP) {
+            // TODO: Outline generation requires alpha extraction, which is costly for
+            // hardware bitmaps. Instead use canvas layer operations once its available.
+            drawable.draw(mCanvas);
+        }
         mCanvas.restoreToCount(saveCount);
         mCanvas.setBitmap(null);
 
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 1eddb2c..8c4738c 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Provider;
 
 /**
@@ -132,7 +133,12 @@
         Bitmap bitmap = createIconBitmap(icon, context, scale);
         if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
                 icon instanceof AdaptiveIconDrawable) {
-            bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
+            synchronized (sCanvas) {
+                sCanvas.setBitmap(bitmap);
+                ShadowGenerator.getInstance(context).recreateIcon(
+                        Bitmap.createBitmap(bitmap), sCanvas);
+                sCanvas.setBitmap(null);
+            }
         }
 
         if (user != null && !Process.myUserHandle().equals(user)) {
@@ -183,16 +189,25 @@
         return createIconBitmap(icon, context, scale);
     }
 
-    public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) {
-        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+    /**
+     * 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(srcTgt);
-            int iconSize = srcTgt.getWidth();
-            badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
-            badge.draw(sCanvas);
+            sCanvas.setBitmap(target);
+            badgeWithDrawable(sCanvas, badge, context);
             sCanvas.setBitmap(null);
         }
-        return srcTgt;
+    }
+
+    /**
+     * 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);
+        badge.draw(target);
     }
 
     /**
@@ -326,9 +341,16 @@
         if (!badged) {
             return unbadgedBitmap;
         }
-        unbadgedBitmap = ShadowGenerator.getInstance(context).recreateIcon(unbadgedBitmap);
-        return badgeWithDrawable(unbadgedBitmap,
-                new FastBitmapDrawable(getShortcutInfoBadge(shortcutInfo, cache)), context);
+
+        int size = app.getInvariantDeviceProfile().iconBitmapSize;
+
+        final Bitmap unbadgedfinal = unbadgedBitmap;
+        final Bitmap badge = getShortcutInfoBadge(shortcutInfo, cache);
+
+        return UiFactory.createFromRenderer(size, size, false, (c) -> {
+            ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
+            badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
+        });
     }
 
     public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 60eeef5..08be4c9 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -50,49 +50,38 @@
 
     private final int mIconSize;
 
-    private final Canvas mCanvas;
     private final Paint mBlurPaint;
     private final Paint mDrawPaint;
     private final BlurMaskFilter mDefaultBlurMaskFilter;
 
     private ShadowGenerator(Context context) {
         mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
-        mCanvas = new Canvas();
         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);
     }
 
-    public synchronized Bitmap recreateIcon(Bitmap icon) {
-        return recreateIcon(icon, true, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA,
-                KEY_SHADOW_ALPHA);
+    public synchronized void recreateIcon(Bitmap icon, Canvas out) {
+        recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
     }
 
-    public synchronized Bitmap recreateIcon(Bitmap icon, boolean resize,
-            BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha) {
-        int width = resize ? mIconSize : icon.getWidth();
-        int height = resize ? mIconSize : icon.getHeight();
+    public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
+            int ambientAlpha, int keyAlpha, Canvas out) {
         int[] offset = new int[2];
-
         mBlurPaint.setMaskFilter(blurMaskFilter);
         Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
-        Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888);
-        mCanvas.setBitmap(result);
 
         // Draw ambient shadow
         mDrawPaint.setAlpha(ambientAlpha);
-        mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
+        out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
 
         // Draw key shadow
         mDrawPaint.setAlpha(keyAlpha);
-        mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
+        out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
 
         // Draw the icon
         mDrawPaint.setAlpha(255);
-        mCanvas.drawBitmap(icon, 0, 0, mDrawPaint);
-
-        mCanvas.setBitmap(null);
-        return result;
+        out.drawBitmap(icon, 0, 0, mDrawPaint);
     }
 
     public static ShadowGenerator getInstance(Context context) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 2e29015..fc81e80 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,14 +18,19 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 
 public class UiFactory {
 
+    public static final boolean USE_HARDWARE_BITMAP = false;
+
     public static TouchController[] createTouchControllers(Launcher launcher) {
         return new TouchController[] {
                 new AllAppsSwipeController(launcher), new PinchToOverviewListener(launcher)};
@@ -44,4 +49,11 @@
     public static void onWorkspaceLongPress(Launcher launcher) {
         launcher.getStateManager().goToState(OVERVIEW);
     }
+
+    public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
+            BitmapRenderer renderer) {
+        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        renderer.render(new Canvas(result));
+        return result;
+    }
 }