From 9d32323cac8df6152db22d119a61fe2ddc5ada80 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 8 Nov 2017 16:52:34 -0800 Subject: [PATCH] First pass at using hardware bitmaps Bug: 35428783 Change-Id: Ife67b85f6e7e268826597ed9bccd9659841f67de --- .../launcher3/uioverrides/UiFactory.java | 18 ++++++++ src/com/android/launcher3/IconCache.java | 15 ++++++- .../dragndrop/FolderAdaptiveIcon.java | 44 +++++++++---------- .../launcher3/graphics/BitmapRenderer.java | 23 ++++++++++ .../graphics/DragPreviewProvider.java | 24 +++++----- .../graphics/HolographicOutlineHelper.java | 7 ++- .../launcher3/graphics/LauncherIcons.java | 44 ++++++++++++++----- .../launcher3/graphics/ShadowGenerator.java | 25 +++-------- .../launcher3/uioverrides/UiFactory.java | 12 +++++ 9 files changed, 143 insertions(+), 69 deletions(-) create mode 100644 src/com/android/launcher3/graphics/BitmapRenderer.java diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java index 9178d8ad42..9be0d32e54 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 android.widget.Toast; 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 @@ public class UiFactory { } 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/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 573e8a2563..ab853e5a22 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -41,17 +41,20 @@ import android.os.UserHandle; 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 @@ public class IconCache { @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 @@ public class IconCache { // 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 @@ public class IconCache { 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 @@ public class IconCache { 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 c90546088b..1c6f77cc80 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.MainThreadExecutor; 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 class FolderAdaptiveIcon extends AdaptiveIconDrawable { } 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 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 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() { - @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 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { * 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 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 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 0000000000..4652ded16c --- /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 355c231f3e..902906f4b7 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.LauncherAppWidgetHostView; 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 @@ public class DragPreviewProvider { /** * 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 @@ public class DragPreviewProvider { 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 @@ public class DragPreviewProvider { 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 fdf2d679a9..ebfe1e7653 100644 --- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java @@ -33,6 +33,7 @@ import android.util.SparseArray; 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 @@ public class HolographicOutlineHelper { 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 1eddb2cf04..8c4738cd2b 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.config.FeatureFlags; 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 @@ public class LauncherIcons { 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 @@ public class LauncherIcons { 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 @@ public class LauncherIcons { 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 60eeef5dfd..08be4c9288 100644 --- a/src/com/android/launcher3/graphics/ShadowGenerator.java +++ b/src/com/android/launcher3/graphics/ShadowGenerator.java @@ -50,49 +50,38 @@ public class ShadowGenerator { 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 2e2901534a..fc81e80421 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 @@ package com.android.launcher3.uioverrides; 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 class UiFactory { 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; + } }