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

Commit 80288de9 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add app badging to conversation icons"

parents 575d7ad9 01c9da47
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();