Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 71d2ccff authored by Ats Jenk's avatar Ats Jenk
Browse files

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
parent 45dd38db
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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);
+121 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+14 −4
Original line number Diff line number Diff line
@@ -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()) {
            b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
                    mBubbleBadgeIconFactory,
                    false /* skipInflation */);
        }
        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 */);
    }

    /**
+0 −90
Original line number Diff line number Diff line
@@ -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;
    }
}
+8 −4
Original line number Diff line number Diff line
@@ -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
Loading