Merge "Use a separate icon factory for app badge icons"
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user