From 71d2ccffc71e9121ea5b2d3bef56969c690d9f0d Mon Sep 17 00:00:00 2001 From: Ats Jenk Date: Thu, 6 Jan 2022 11:35:42 -0800 Subject: [PATCH] Use a separate icon factory for app badge icons We were reusing the same BubbleIconFactory for bubble icons and app badge icons. BubbleIconFactory extends BaseIconFactory and resizes all created icons to one size. BubbleIconFactory was configured to resize icons to the size of bubbles as defined by bubble_size dimensions, currently at 60dp. Badge icon size is defined by bubble_badge_size and is currently at 24dp. Using the same BubbleIconFactory for both icons meant that we were resizing all badge icons to be the same size as bubbles, at first. And then, when actually using them, sizing them down again. This change introduces a separate BubbleBadgeIconFactory with relevant code moved from BubbleIconFactory. And mainly, it defines the icon size as the size of the badge. Avoiding resizing. Test: visually verified that the badge icon looks the same Bug: 189173831 Change-Id: Ie032bbc15eb0fd3e78636796bafb05149e262850 --- .../com/android/wm/shell/bubbles/Bubble.java | 5 +- .../shell/bubbles/BubbleBadgeIconFactory.java | 121 ++++++++++++++++++ .../wm/shell/bubbles/BubbleController.java | 18 ++- .../wm/shell/bubbles/BubbleIconFactory.java | 90 ------------- .../wm/shell/bubbles/BubbleViewInfoTask.java | 12 +- .../android/systemui/wmshell/BubblesTest.java | 2 + 6 files changed, 149 insertions(+), 99 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index eaeade82ebb6a..cf4647a09a58a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -364,13 +364,15 @@ public class Bubble implements BubbleViewProvider { * @param context the context for the bubble. * @param controller the bubble controller. * @param stackView the stackView the bubble is eventually added to. - * @param iconFactory the iconfactory use to create badged images for the bubble. + * @param iconFactory the icon factory use to create images for the bubble. + * @param badgeIconFactory the icon factory to create app badges for the bubble. */ void inflate(BubbleViewInfoTask.Callback callback, Context context, BubbleController controller, BubbleStackView stackView, BubbleIconFactory iconFactory, + BubbleBadgeIconFactory badgeIconFactory, boolean skipInflation) { if (isBubbleLoading()) { mInflationTask.cancel(true /* mayInterruptIfRunning */); @@ -380,6 +382,7 @@ public class Bubble implements BubbleViewProvider { controller, stackView, iconFactory, + badgeIconFactory, skipInflation, callback, mMainExecutor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java new file mode 100644 index 0000000000000..4eeb20769e091 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 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.wm.shell.bubbles; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; + +import com.android.launcher3.icons.BaseIconFactory; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.ShadowGenerator; +import com.android.wm.shell.R; + +/** + * Factory for creating app badge icons that are shown on bubbles. + */ +public class BubbleBadgeIconFactory extends BaseIconFactory { + + public BubbleBadgeIconFactory(Context context) { + super(context, context.getResources().getConfiguration().densityDpi, + context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size)); + } + + /** + * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This + * will include the workprofile indicator on the badge if appropriate. + */ + BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) { + ShadowGenerator shadowGenerator = new ShadowGenerator(mIconBitmapSize); + Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mIconBitmapSize); + + if (userBadgedAppIcon instanceof AdaptiveIconDrawable) { + userBadgedBitmap = Bitmap.createScaledBitmap( + getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */ + userBadgedAppIcon.getIntrinsicWidth()), + mIconBitmapSize, mIconBitmapSize, /* filter */ true); + } + + if (isImportantConversation) { + final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width); + final int importantConversationColor = mContext.getResources().getColor( + R.color.important_conversation, null); + Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(), + userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig()); + Canvas c = new Canvas(badgeAndRing); + + Paint ringPaint = new Paint(); + ringPaint.setStyle(Paint.Style.FILL); + ringPaint.setColor(importantConversationColor); + ringPaint.setAntiAlias(true); + c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2, ringPaint); + + final int bitmapTop = (int) ringStrokeWidth; + final int bitmapLeft = (int) ringStrokeWidth; + final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth; + final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth; + + Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth, + bitmapHeight, /* filter */ true); + c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null); + + shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c); + return createIconBitmap(badgeAndRing); + } else { + Canvas c = new Canvas(); + c.setBitmap(userBadgedBitmap); + shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c); + return createIconBitmap(userBadgedBitmap); + } + } + + private Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) { + Drawable foreground = icon.getForeground(); + Drawable background = icon.getBackground(); + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(); + canvas.setBitmap(bitmap); + + // Clip canvas to circle. + Path circlePath = new Path(); + circlePath.addCircle(/* x */ size / 2f, + /* y */ size / 2f, + /* radius */ size / 2f, + Path.Direction.CW); + canvas.clipPath(circlePath); + + // Draw background. + background.setBounds(0, 0, size, size); + background.draw(canvas); + + // Draw foreground. The foreground and background drawables are derived from adaptive icons + // Some icon shapes fill more space than others, so adaptive icons are normalized to about + // the same size. This size is smaller than the original bounds, so we estimate + // the difference in this offset. + int offset = size / 5; + foreground.setBounds(-offset, -offset, size + offset, size + offset); + foreground.draw(canvas); + + canvas.setBitmap(null); + return bitmap; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 2d1d7cdae2257..250cbe94ef300 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -151,6 +151,7 @@ public class BubbleController { private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; private BubbleIconFactory mBubbleIconFactory; + private BubbleBadgeIconFactory mBubbleBadgeIconFactory; private BubblePositioner mBubblePositioner; private Bubbles.SysuiProxy mSysuiProxy; @@ -278,6 +279,7 @@ public class BubbleController { mBubbleData = data; mSavedBubbleKeysPerUser = new SparseSetArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; mOneHandedOptional = oneHandedOptional; @@ -500,6 +502,7 @@ public class BubbleController { } mStackView.updateStackPosition(); mBubbleIconFactory = new BubbleIconFactory(mContext); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); mStackView.onDisplaySizeChanged(); } if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) { @@ -778,13 +781,17 @@ public class BubbleController { mStackView.onThemeChanged(); } mBubbleIconFactory = new BubbleIconFactory(mContext); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); + // Reload each bubble - for (Bubble b: mBubbleData.getBubbles()) { + for (Bubble b : mBubbleData.getBubbles()) { b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory, + mBubbleBadgeIconFactory, false /* skipInflation */); } - for (Bubble b: mBubbleData.getOverflowBubbles()) { + for (Bubble b : mBubbleData.getOverflowBubbles()) { b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory, + mBubbleBadgeIconFactory, false /* skipInflation */); } } @@ -800,6 +807,7 @@ public class BubbleController { mScreenBounds.set(newConfig.windowConfiguration.getBounds()); mBubbleData.onMaxBubblesChanged(); mBubbleIconFactory = new BubbleIconFactory(mContext); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); mStackView.onDisplaySizeChanged(); } if (newConfig.fontScale != mFontScale) { @@ -961,7 +969,8 @@ public class BubbleController { } bubble.inflate( (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble), - mContext, this, mStackView, mBubbleIconFactory, true /* skipInflation */); + mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory, + true /* skipInflation */); }); return null; }); @@ -996,7 +1005,8 @@ public class BubbleController { ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), - mContext, this, mStackView, mBubbleIconFactory, false /* skipInflation */); + mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory, + false /* skipInflation */); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java index b0e029fdc6811..9d3bf34895d31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java @@ -21,19 +21,12 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import androidx.annotation.VisibleForTesting; import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.ShadowGenerator; import com.android.wm.shell.R; /** @@ -44,12 +37,9 @@ import com.android.wm.shell.R; @VisibleForTesting public class BubbleIconFactory extends BaseIconFactory { - private int mBadgeSize; - public BubbleIconFactory(Context context) { super(context, context.getResources().getConfiguration().densityDpi, context.getResources().getDimensionPixelSize(R.dimen.bubble_size)); - mBadgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size); } /** @@ -75,84 +65,4 @@ public class BubbleIconFactory extends BaseIconFactory { return null; } } - - /** - * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This - * will include the workprofile indicator on the badge if appropriate. - */ - BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) { - ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize); - Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize); - - if (userBadgedAppIcon instanceof AdaptiveIconDrawable) { - userBadgedBitmap = Bitmap.createScaledBitmap( - getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */ - userBadgedAppIcon.getIntrinsicWidth()), - mBadgeSize, mBadgeSize, /* filter */ true); - } - - if (isImportantConversation) { - final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.importance_ring_stroke_width); - final int importantConversationColor = mContext.getResources().getColor( - R.color.important_conversation, null); - Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(), - userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig()); - Canvas c = new Canvas(badgeAndRing); - - Paint ringPaint = new Paint(); - ringPaint.setStyle(Paint.Style.FILL); - ringPaint.setColor(importantConversationColor); - ringPaint.setAntiAlias(true); - c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2, ringPaint); - - final int bitmapTop = (int) ringStrokeWidth; - final int bitmapLeft = (int) ringStrokeWidth; - final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth; - final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth; - - Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth, - bitmapHeight, /* filter */ true); - c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null); - - shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c); - return createIconBitmap(badgeAndRing); - } else { - Canvas c = new Canvas(); - c.setBitmap(userBadgedBitmap); - shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c); - return createIconBitmap(userBadgedBitmap); - } - } - - public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) { - Drawable foreground = icon.getForeground(); - Drawable background = icon.getBackground(); - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(); - canvas.setBitmap(bitmap); - - // Clip canvas to circle. - Path circlePath = new Path(); - circlePath.addCircle(/* x */ size / 2f, - /* y */ size / 2f, - /* radius */ size / 2f, - Path.Direction.CW); - canvas.clipPath(circlePath); - - // Draw background. - background.setBounds(0, 0, size, size); - background.draw(canvas); - - // Draw foreground. The foreground and background drawables are derived from adaptive icons - // Some icon shapes fill more space than others, so adaptive icons are normalized to about - // the same size. This size is smaller than the original bounds, so we estimate - // the difference in this offset. - int offset = size / 5; - foreground.setBounds(-offset, -offset, size + offset, size + offset); - foreground.draw(canvas); - - canvas.setBitmap(null); - return bitmap; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index b01c756c5a7ec..69762c9bc06a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -71,6 +71,7 @@ public class BubbleViewInfoTask extends AsyncTask mController; private WeakReference mStackView; private BubbleIconFactory mIconFactory; + private BubbleBadgeIconFactory mBadgeIconFactory; private boolean mSkipInflation; private Callback mCallback; private Executor mMainExecutor; @@ -84,6 +85,7 @@ public class BubbleViewInfoTask extends AsyncTask(controller); mStackView = new WeakReference<>(stackView); mIconFactory = factory; + mBadgeIconFactory = badgeFactory; mSkipInflation = skipInflation; mCallback = c; mMainExecutor = mainExecutor; @@ -100,7 +103,7 @@ public class BubbleViewInfoTask extends AsyncTask