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 7903a5102ddec..22bec3d63bcf0 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