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

Commit 5bf2bc35 authored by Ats Jenk's avatar Ats Jenk Committed by Android (Google) Code Review
Browse files

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

parents 7d24159b 71d2ccff
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