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

Commit 01c9da47 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Add app badging to conversation icons

Test: atest, manual
Fixes: 149573346
Change-Id: I97dfdb86f00012598b836c32603c8b5057abdf00
parent 0a9d74fc
Loading
Loading
Loading
Loading
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.settingslib.notification;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.IconDrawableFactory;

import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ShadowGenerator;

/**
 * Factory for creating normalized conversation icons.
 * We are not using Launcher's IconFactory because conversation rendering only runs on the UI
 * thread, so there is no need to manage a pool across multiple threads.
 */
public class ConversationIconFactory extends BaseIconFactory {

    final LauncherApps mLauncherApps;
    final PackageManager mPackageManager;
    final IconDrawableFactory mIconDrawableFactory;

    public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
            IconDrawableFactory iconDrawableFactory, int iconSizePx) {
        super(context, context.getResources().getConfiguration().densityDpi,
                iconSizePx);
        mLauncherApps = la;
        mPackageManager = pm;
        mIconDrawableFactory = iconDrawableFactory;
    }

    private int getBadgeSize() {
        return mContext.getResources().getDimensionPixelSize(
                com.android.launcher3.icons.R.dimen.profile_badge_size);
    }
    /**
     * Returns the conversation info drawable
     */
    private Drawable getConversationDrawable(ShortcutInfo shortcutInfo) {
        return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
    }

    /**
     * Get the {@link Drawable} that represents the app icon
     */
    private Drawable getBadgedIcon(String packageName, int userId) {
        try {
            final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
                    packageName, PackageManager.GET_META_DATA, userId);
            return mIconDrawableFactory.getBadgedIcon(appInfo, userId);
        } catch (PackageManager.NameNotFoundException e) {
            return mPackageManager.getDefaultActivityIcon();
        }
    }

    /**
     * Turns a Drawable into a Bitmap
     */
    BitmapInfo toBitmap(Drawable userBadgedAppIcon) {
        Bitmap bitmap = createIconBitmap(
                userBadgedAppIcon, 1f, getBadgeSize());

        Canvas c = new Canvas();
        ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
        c.setBitmap(bitmap);
        shadowGenerator.recreateIcon(Bitmap.createBitmap(bitmap), c);
        return createIconBitmap(bitmap);
    }

    /**
     * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
     */
    public Bitmap getConversationBitmap(ShortcutInfo info, String packageName, int uid) {
        return getConversationBitmap(getConversationDrawable(info), packageName, uid);
    }

    /**
     * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
     */
    public Bitmap getConversationBitmap(Drawable baseIcon, String packageName, int uid) {
        int userId = UserHandle.getUserId(uid);
        Drawable badge = getBadgedIcon(packageName, userId);
        BitmapInfo iconInfo = createBadgedIconBitmap(baseIcon,
                UserHandle.of(userId),
                true /* shrinkNonAdaptiveIcons */);

        badgeWithDrawable(iconInfo.icon,
                new BitmapDrawable(mContext.getResources(), toBitmap(badge).icon));
        return iconInfo.icon;
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -33,11 +33,9 @@ import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
@@ -61,6 +59,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -87,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements
    private PackageManager mPm;
    private VisualStabilityManager mVisualStabilityManager;
    private ShadeController mShadeController;
    private ConversationIconFactory mIconFactory;

    private String mPackageName;
    private String mAppName;
@@ -186,6 +186,7 @@ public class NotificationConversationInfo extends LinearLayout implements
            OnSettingsClickListener onSettingsClick,
            OnAppSettingsClickListener onAppSettingsClick,
            OnSnoozeClickListener onSnoozeClickListener,
            ConversationIconFactory conversationIconFactory,
            boolean isDeviceProvisioned) {
        mSelectedAction = -1;
        mINotificationManager = iNotificationManager;
@@ -203,6 +204,7 @@ public class NotificationConversationInfo extends LinearLayout implements
        mIsDeviceProvisioned = isDeviceProvisioned;
        mOnSnoozeClickListener = onSnoozeClickListener;
        mShadeController = Dependency.get(ShadeController.class);
        mIconFactory = conversationIconFactory;

        mShortcutManager = shortcutManager;
        mLauncherApps = launcherApps;
@@ -320,8 +322,8 @@ public class NotificationConversationInfo extends LinearLayout implements
    private void bindIcon() {
        ImageView image = findViewById(R.id.conversation_icon);
        if (mShortcutInfo != null) {
            image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
                    mContext.getResources().getDisplayMetrics().densityDpi));
            image.setImageBitmap(mIconFactory.getConversationBitmap(
                    mShortcutInfo, mPackageName, mAppUid));
        } else {
            if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
                // TODO: maybe use a generic group icon, or a composite of recent senders
+8 −2
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
@@ -42,8 +43,10 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -58,7 +61,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;

@@ -388,6 +390,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
                notificationInfoView.closeControls(v, false);
            };
        }
        ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
                launcherApps, pmUser, IconDrawableFactory.newInstance(mContext),
                mContext.getResources().getDimensionPixelSize(
                        R.dimen.notification_guts_conversation_icon_size));

        notificationInfoView.bindNotification(
                shortcutManager,
@@ -401,8 +407,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
                onSettingsClick,
                onAppSettingsClick,
                onSnoozeClickListener,
                iconFactoryLoader,
                mDeviceProvisionedController.isDeviceProvisioned());

    }

    /**
+35 −11
Original line number Diff line number Diff line
@@ -51,7 +51,8 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Drawable;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -60,12 +61,12 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -112,7 +113,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
    private NotificationEntry mBubbleEntry;
    @Mock
    private ShortcutInfo mShortcutInfo;
    private Drawable mImage;
    @Mock
    private Bitmap mImage;

    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();
@@ -134,6 +136,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
    private NotificationGuts mNotificationGuts;
    @Mock
    private ShadeController mShadeController;
    @Mock
    private ConversationIconFactory mIconFactory;

    @Before
    public void setUp() throws Exception {
@@ -176,9 +180,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
        when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
        List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
        mImage = mContext.getDrawable(R.drawable.ic_remove);
        when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
                anyInt())).thenReturn(mImage);
        when(mIconFactory.getConversationBitmap(any(ShortcutInfo.class), anyString(), anyInt()))
                .thenReturn(mImage);

        mNotificationChannel = new NotificationChannel(
                TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
@@ -224,9 +227,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
        assertEquals(mImage, view.getDrawable());
        assertEquals(mImage, ((BitmapDrawable) view.getDrawable()).getBitmap());
    }

    @Test
@@ -244,6 +248,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
        assertTrue(textView.getText().toString().contains("App Name"));
@@ -290,6 +295,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
        assertTrue(textView.getText().toString().contains(group.getName()));
@@ -312,6 +318,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -333,6 +340,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
        assertEquals(GONE, nameView.getVisibility());
@@ -361,6 +369,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
        assertEquals(VISIBLE, nameView.getVisibility());
@@ -385,6 +394,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                },
                null,
                null,
                mIconFactory,
                true);

        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -407,6 +417,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);
        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -430,6 +441,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                },
                null,
                null,
                mIconFactory,
                false);
        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -449,6 +461,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -469,6 +482,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -491,9 +505,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);


        // Promote it
        mNotificationInfo.findViewById(R.id.home).performClick();
        mTestableLooper.processAllMessages();
@@ -522,9 +536,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                (View v, int hours) -> {
                    latch.countDown();
                },
                mIconFactory,
                true);


        // Promote it
        mNotificationInfo.findViewById(R.id.snooze).performClick();
        mTestableLooper.processAllMessages();
@@ -551,6 +565,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        assertFalse(mBubbleEntry.isBubble());
@@ -583,6 +598,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        assertTrue(mBubbleEntry.isBubble());
@@ -613,6 +629,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        assertFalse(mBubbleEntry.isBubble());
@@ -641,9 +658,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);


        ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
        assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
                fave.getContentDescription().toString());
@@ -675,6 +692,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
@@ -708,6 +726,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
@@ -743,9 +762,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);


        ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
        assertEquals(mContext.getString(R.string.notification_conversation_mute),
                mute.getContentDescription().toString());
@@ -774,7 +793,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
                anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
    }
@@ -794,7 +815,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
                anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
    }
@@ -815,6 +838,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
                null,
                null,
                null,
                mIconFactory,
                true);

        mNotificationInfo.findViewById(R.id.mute).performClick();