Add app badging to conversation icons

Test: atest, manual
Fixes: 149573346
Change-Id: I97dfdb86f00012598b836c32603c8b5057abdf00
This commit is contained in:
Julia Reynolds
2020-02-14 14:17:58 -05:00
parent 0a9d74fc03
commit 01c9da477e
4 changed files with 162 additions and 17 deletions

View File

@@ -0,0 +1,113 @@
/*
* 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;
}
}

View File

@@ -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

View File

@@ -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());
}
/**

View File

@@ -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();