Merge "Use a separate icon factory for app badge icons"

This commit is contained in:
Ats Jenk
2022-01-10 19:55:51 +00:00
committed by Android (Google) Code Review
6 changed files with 149 additions and 99 deletions

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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 */);
}
/**

View File

@@ -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;
}
}

View File

@@ -71,6 +71,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
private WeakReference<BubbleController> mController;
private WeakReference<BubbleStackView> 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<Void, Void, BubbleViewInfoTask
BubbleController controller,
BubbleStackView stackView,
BubbleIconFactory factory,
BubbleBadgeIconFactory badgeFactory,
boolean skipInflation,
Callback c,
Executor mainExecutor) {
@@ -92,6 +94,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
mController = new WeakReference<>(controller);
mStackView = new WeakReference<>(stackView);
mIconFactory = factory;
mBadgeIconFactory = badgeFactory;
mSkipInflation = skipInflation;
mCallback = c;
mMainExecutor = mainExecutor;
@@ -100,7 +103,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
mIconFactory, mBubble, mSkipInflation);
mIconFactory, mBadgeIconFactory, mBubble, mSkipInflation);
}
@Override
@@ -135,7 +138,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@VisibleForTesting
@Nullable
public static BubbleViewInfo populate(Context c, BubbleController controller,
BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
BubbleStackView stackView, BubbleIconFactory iconFactory,
BubbleBadgeIconFactory badgeIconFactory, Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
@@ -187,11 +191,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
bubbleDrawable = appIcon;
}
BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
BitmapInfo badgeBitmapInfo = badgeIconFactory.getBadgeBitmap(badgedIcon,
b.isImportantConversation());
info.badgeBitmap = badgeBitmapInfo.icon;
// Raw badge bitmap never includes the important conversation ring
info.mRawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
info.mRawBadgeBitmap = badgeIconFactory.getBadgeBitmap(badgedIcon, false).icon;
info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon;
// Dot color & placement

View File

@@ -112,6 +112,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleBadgeIconFactory;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
@@ -1217,6 +1218,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleController,
mBubbleController.getStackView(),
new BubbleIconFactory(mContext),
new BubbleBadgeIconFactory(mContext),
bubble,
true /* skipInflation */);
verify(userContext, times(1)).getPackageManager();