decouple Bubble from NotificationEntry

1. decouple Bubble from NotificationEntry
2. save title from Bubble into BubbleEntity
3. copied boolean values from NotificationEntry into Bubble for UI
variants (e.g. isVisuallyInterruptive, isClearable,
shouldSuppressNotificationDot... e.t.c)

Bug: 151474524
Change-Id: I606c6ff93b3dc3867b4d0a6129d7117d9999c170
Test: manual
This commit is contained in:
Pinyao Ting
2020-05-29 17:50:34 -07:00
parent bcdd5c5321
commit d07c2deaf0
19 changed files with 324 additions and 288 deletions

View File

@@ -15,7 +15,6 @@
*/
package com.android.systemui.bubbles;
import static android.app.Notification.FLAG_BUBBLE;
import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
@@ -29,21 +28,19 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -57,17 +54,12 @@ import java.util.Objects;
class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
/**
* NotificationEntry associated with the bubble. A null value implies this bubble is loaded
* from disk.
*/
@Nullable
private NotificationEntry mEntry;
private final String mKey;
private long mLastUpdated;
private long mLastAccessed;
@Nullable
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
/** Whether the bubble should show a dot for the notification indicating updated content. */
@@ -75,8 +67,6 @@ class Bubble implements BubbleViewProvider {
/** Whether flyout text should be suppressed, regardless of any other flags or state. */
private boolean mSuppressFlyout;
/** Whether this bubble should auto expand regardless of the normal flag, used for overflow. */
private boolean mShouldAutoExpand;
// Items that are typically loaded later
private String mAppName;
@@ -92,6 +82,7 @@ class Bubble implements BubbleViewProvider {
* Presentational info about the flyout.
*/
public static class FlyoutMessage {
@Nullable public Icon senderIcon;
@Nullable public Drawable senderAvatar;
@Nullable public CharSequence senderName;
@Nullable public CharSequence message;
@@ -109,16 +100,39 @@ class Bubble implements BubbleViewProvider {
private UserHandle mUser;
@NonNull
private String mPackageName;
@Nullable
private String mTitle;
@Nullable
private Icon mIcon;
private boolean mIsBubble;
private boolean mIsVisuallyInterruptive;
private boolean mIsClearable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
private int mDesiredHeight;
@DimenRes
private int mDesiredHeightResId;
/** for logging **/
@Nullable
private InstanceId mInstanceId;
@Nullable
private String mChannelId;
private int mNotificationId;
private int mAppUid = -1;
@Nullable
private PendingIntent mIntent;
@Nullable
private PendingIntent mDeleteIntent;
/**
* Create a bubble with limited information based on given {@link ShortcutInfo}.
* Note: Currently this is only being used when the bubble is persisted to disk.
*/
Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
final int desiredHeight, final int desiredHeightResId) {
final int desiredHeight, final int desiredHeightResId, @Nullable final String title) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
mShortcutInfo = shortcutInfo;
@@ -126,8 +140,10 @@ class Bubble implements BubbleViewProvider {
mFlags = 0;
mUser = shortcutInfo.getUserHandle();
mPackageName = shortcutInfo.getPackage();
mIcon = shortcutInfo.getIcon();
mDesiredHeight = desiredHeight;
mDesiredHeightResId = desiredHeightResId;
mTitle = title;
}
/** Used in tests when no UI is required. */
@@ -145,12 +161,6 @@ class Bubble implements BubbleViewProvider {
return mKey;
}
@Nullable
public NotificationEntry getEntry() {
return mEntry;
}
@NonNull
public UserHandle getUser() {
return mUser;
}
@@ -203,14 +213,7 @@ class Bubble implements BubbleViewProvider {
@Nullable
public String getTitle() {
final CharSequence titleCharSeq;
if (mEntry == null) {
titleCharSeq = null;
} else {
titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence(
Notification.EXTRA_TITLE);
}
return titleCharSeq != null ? titleCharSeq.toString() : null;
return mTitle;
}
/**
@@ -331,17 +334,44 @@ class Bubble implements BubbleViewProvider {
void setEntry(@NonNull final NotificationEntry entry) {
Objects.requireNonNull(entry);
Objects.requireNonNull(entry.getSbn());
mEntry = entry;
mLastUpdated = entry.getSbn().getPostTime();
mFlags = entry.getSbn().getNotification().flags;
mIsBubble = entry.getSbn().getNotification().isBubbleNotification();
mPackageName = entry.getSbn().getPackageName();
mUser = entry.getSbn().getUser();
mTitle = getTitle(entry);
mIsClearable = entry.isClearable();
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
mShouldSuppressPeek = entry.shouldSuppressPeek();
mChannelId = entry.getSbn().getNotification().getChannelId();
mNotificationId = entry.getSbn().getId();
mAppUid = entry.getSbn().getUid();
mInstanceId = entry.getSbn().getInstanceId();
mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry);
if (entry.getRanking() != null) {
mShortcutInfo = entry.getRanking().getShortcutInfo() != null
? entry.getRanking().getShortcutInfo() : mShortcutInfo;
mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
}
if (entry.getBubbleMetadata() != null) {
mFlags = entry.getBubbleMetadata().getFlags();
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
mIcon = entry.getBubbleMetadata().getIcon();
mIntent = entry.getBubbleMetadata().getIntent();
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
}
@Nullable
Icon getIcon() {
return mIcon;
}
boolean isVisuallyInterruptive() {
return mIsVisuallyInterruptive;
}
/**
* @return the last time this bubble was updated or accessed, whichever is most recent.
*/
@@ -364,6 +394,19 @@ class Bubble implements BubbleViewProvider {
return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
}
public InstanceId getInstanceId() {
return mInstanceId;
}
@Nullable
public String getChannelId() {
return mChannelId;
}
public int getNotificationId() {
return mNotificationId;
}
/**
* Should be invoked whenever a Bubble is accessed (selected while expanded).
*/
@@ -384,24 +427,19 @@ class Bubble implements BubbleViewProvider {
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
if (mEntry == null) return false;
return !shouldSuppressNotification() || !mEntry.isClearable();
return !shouldSuppressNotification() || !mIsClearable;
}
/**
* Sets whether this notification should be suppressed in the shade.
*/
void setSuppressNotification(boolean suppressNotification) {
if (mEntry == null) return;
boolean prevShowInShade = showInShade();
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
int flags = data.getFlags();
if (suppressNotification) {
flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
} else {
flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
}
data.setFlags(flags);
if (showInShade() != prevShowInShade && mSuppressionListener != null) {
mSuppressionListener.onBubbleNotificationSuppressionChange(this);
@@ -424,9 +462,8 @@ class Bubble implements BubbleViewProvider {
*/
@Override
public boolean showDot() {
if (mEntry == null) return false;
return mShowBubbleUpdateDot
&& !mEntry.shouldSuppressNotificationDot()
&& !mShouldSuppressNotificationDot
&& !shouldSuppressNotification();
}
@@ -434,10 +471,9 @@ class Bubble implements BubbleViewProvider {
* Whether the flyout for the bubble should be shown.
*/
boolean showFlyout() {
if (mEntry == null) return false;
return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
return !mSuppressFlyout && !mShouldSuppressPeek
&& !shouldSuppressNotification()
&& !mEntry.shouldSuppressNotificationList();
&& !mShouldSuppressNotificationList;
}
/**
@@ -480,25 +516,14 @@ class Bubble implements BubbleViewProvider {
}
}
/**
* Whether shortcut information should be used to populate the bubble.
* <p>
* To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
* To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
*/
boolean usingShortcutInfo() {
return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null
|| mShortcutInfo != null;
@Nullable
PendingIntent getBubbleIntent() {
return mIntent;
}
@Nullable
PendingIntent getBubbleIntent() {
if (mEntry == null) return null;
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
if (data != null) {
return data.getIntent();
}
return null;
PendingIntent getDeleteIntent() {
return mDeleteIntent;
}
Intent getSettingsIntent(final Context context) {
@@ -514,8 +539,12 @@ class Bubble implements BubbleViewProvider {
return intent;
}
public int getAppUid() {
return mAppUid;
}
private int getUid(final Context context) {
if (mEntry != null) return mEntry.getSbn().getUid();
if (mAppUid != -1) return mAppUid;
final PackageManager pm = context.getPackageManager();
if (pm == null) return -1;
try {
@@ -548,24 +577,27 @@ class Bubble implements BubbleViewProvider {
}
private boolean shouldSuppressNotification() {
if (mEntry == null) return true;
return mEntry.getBubbleMetadata() != null
&& mEntry.getBubbleMetadata().isNotificationSuppressed();
return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
}
boolean shouldAutoExpand() {
if (mEntry == null) return mShouldAutoExpand;
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand;
public boolean shouldAutoExpand() {
return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
void setShouldAutoExpand(boolean shouldAutoExpand) {
mShouldAutoExpand = shouldAutoExpand;
if (shouldAutoExpand) {
enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
} else {
disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
}
public void setIsBubble(final boolean isBubble) {
mIsBubble = isBubble;
}
public boolean isBubble() {
if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0;
return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0;
return mIsBubble;
}
public void enable(int option) {
@@ -576,6 +608,10 @@ class Bubble implements BubbleViewProvider {
mFlags &= ~option;
}
public boolean isEnabled(int option) {
return (mFlags & option) != 0;
}
@Override
public String toString() {
return "Bubble{" + mKey + '}';
@@ -610,34 +646,24 @@ class Bubble implements BubbleViewProvider {
@Override
public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) {
if (this.getEntry() == null
|| this.getEntry().getSbn() == null) {
SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
null /* package name */,
null /* notification channel */,
0 /* notification ID */,
0 /* bubble position */,
bubbleCount,
action,
normalX,
normalY,
false /* unread bubble */,
false /* on-going bubble */,
false /* isAppForeground (unused) */);
} else {
StatusBarNotification notification = this.getEntry().getSbn();
SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
notification.getPackageName(),
notification.getNotification().getChannelId(),
notification.getId(),
index,
bubbleCount,
action,
normalX,
normalY,
this.showInShade(),
false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
mPackageName,
mChannelId,
mNotificationId,
index,
bubbleCount,
action,
normalX,
normalY,
showInShade(),
false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
@Nullable
private static String getTitle(@NonNull final NotificationEntry e) {
final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence(
Notification.EXTRA_TITLE);
return titleCharSeq == null ? null : titleCharSeq.toString();
}
}

View File

@@ -505,8 +505,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
addNotifCallback(new NotifCallback() {
@Override
public void removeNotification(NotificationEntry entry, int reason) {
mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
reason);
mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason);
}
@Override
@@ -637,8 +636,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mStackView.setExpandListener(mExpandListener);
}
mStackView.setUnbubbleConversationCallback(notificationEntry ->
onUserChangedBubble(notificationEntry, false /* shouldBubble */));
mStackView.setUnbubbleConversationCallback(key -> {
final NotificationEntry entry =
mNotificationEntryManager.getPendingOrActiveNotif(key);
if (entry != null) {
onUserChangedBubble(entry, false /* shouldBubble */);
}
});
}
addToWindowManagerMaybe();
@@ -1024,10 +1028,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* @param entry the notification to change bubble state for.
* @param shouldBubble whether the notification should show as a bubble or not.
*/
public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) {
if (entry == null) {
return;
}
public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
NotificationChannel channel = entry.getChannel();
final String appPkg = entry.getSbn().getPackageName();
final int appUid = entry.getSbn().getUid();
@@ -1103,7 +1104,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleData.removeSuppressedSummary(groupKey);
// Remove any associated bubble children with the summary
final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
groupKey, mNotificationEntryManager);
for (int i = 0; i < bubbleChildren.size(); i++) {
removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
}
@@ -1161,21 +1163,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
Objects.requireNonNull(b);
if (isBubble) {
b.enable(FLAG_BUBBLE);
} else {
b.disable(FLAG_BUBBLE);
}
if (b.getEntry() != null) {
b.setIsBubble(isBubble);
final NotificationEntry entry = mNotificationEntryManager
.getPendingOrActiveNotif(b.getKey());
if (entry != null) {
// Updating the entry to be a bubble will trigger our normal update flow
setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand());
setIsBubble(entry, isBubble, b.shouldAutoExpand());
} else if (isBubble) {
// If we have no entry to update, it's a persisted bubble so
// we need to add it to the stack ourselves
// If bubble doesn't exist, it's a persisted bubble so we need to add it to the
// stack ourselves
Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
!bubble.shouldAutoExpand() /* showInShade */);
}
}
@@ -1214,6 +1213,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (reason == DISMISS_NOTIF_CANCEL) {
bubblesToBeRemovedFromRepository.add(bubble);
}
final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
bubble.getKey());
if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
&& (!bubble.showInShade()
@@ -1222,26 +1223,27 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// The bubble is now gone & the notification is hidden from the shade, so
// time to actually remove it
for (NotifCallback cb : mCallbacks) {
if (bubble.getEntry() != null) {
cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
if (entry != null) {
cb.removeNotification(entry, REASON_CANCEL);
}
}
} else {
if (bubble.isBubble()) {
setIsBubble(bubble, false /* isBubble */);
}
if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) {
bubble.getEntry().getRow().updateBubbleButton();
if (entry != null && entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
}
}
if (bubble.getEntry() != null) {
final String groupKey = bubble.getEntry().getSbn().getGroupKey();
if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
if (entry != null) {
final String groupKey = entry.getSbn().getGroupKey();
if (mBubbleData.getBubblesInGroup(
groupKey, mNotificationEntryManager).isEmpty()) {
// Time to potentially remove the summary
for (NotifCallback cb : mCallbacks) {
cb.maybeCancelSummary(bubble.getEntry());
cb.maybeCancelSummary(entry);
}
}
}
@@ -1266,9 +1268,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (update.selectionChanged && mStackView != null) {
mStackView.setSelectedBubble(update.selectedBubble);
if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) {
mNotificationGroupManager.updateSuppression(
update.selectedBubble.getEntry());
if (update.selectedBubble != null) {
final NotificationEntry entry = mNotificationEntryManager
.getPendingOrActiveNotif(update.selectedBubble.getKey());
if (entry != null) {
mNotificationGroupManager.updateSuppression(entry);
}
}
}
@@ -1341,7 +1346,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
String groupKey = entry.getSbn().getGroupKey();
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
groupKey, mNotificationEntryManager);
boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
&& mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
@@ -1361,9 +1367,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// As far as group manager is concerned, once a child is no longer shown
// in the shade, it is essentially removed.
Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey());
mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
bubbleChild.setSuppressNotification(true);
bubbleChild.setShowDot(false /* show */);
if (bubbleChild != null) {
final NotificationEntry entry = mNotificationEntryManager
.getPendingOrActiveNotif(bubbleChild.getKey());
if (entry != null) {
mNotificationGroupManager.onEntryRemoved(entry);
}
bubbleChild.setSuppressNotification(true);
bubbleChild.setShowDot(false /* show */);
}
} else {
// non-bubbled children can be removed
for (NotifCallback cb : mCallbacks) {

View File

@@ -22,7 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.util.Log;
@@ -34,6 +33,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
@@ -256,8 +256,7 @@ public class BubbleData {
}
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= bubble.getEntry() == null
|| !bubble.getEntry().getRanking().visuallyInterruptive();
suppressFlyout |= !bubble.isVisuallyInterruptive();
if (prevBubble == null) {
// Create a new bubble
@@ -335,13 +334,15 @@ public class BubbleData {
* Retrieves any bubbles that are part of the notification group represented by the provided
* group key.
*/
ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull
NotificationEntryManager nem) {
ArrayList<Bubble> bubbleChildren = new ArrayList<>();
if (groupKey == null) {
return bubbleChildren;
}
for (Bubble b : mBubbles) {
if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey());
if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
bubbleChildren.add(b);
}
}
@@ -439,9 +440,7 @@ public class BubbleData {
Bubble newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
if (bubbleToRemove.getEntry() != null) {
maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
}
maybeSendDeleteIntent(reason, bubbleToRemove);
}
void overflowBubble(@DismissReason int reason, Bubble bubble) {
@@ -611,21 +610,14 @@ public class BubbleData {
return true;
}
private void maybeSendDeleteIntent(@DismissReason int reason,
@NonNull final NotificationEntry entry) {
if (reason == BubbleController.DISMISS_USER_GESTURE) {
Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata();
PendingIntent deleteIntent = bubbleMetadata != null
? bubbleMetadata.getDeleteIntent()
: null;
if (deleteIntent != null) {
try {
deleteIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to send delete intent for bubble with key: "
+ entry.getKey());
}
}
private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) {
if (reason != BubbleController.DISMISS_USER_GESTURE) return;
PendingIntent deleteIntent = bubble.getDeleteIntent();
if (deleteIntent == null) return;
try {
deleteIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey());
}
}

View File

@@ -74,11 +74,15 @@ internal class BubbleDataRepository @Inject constructor(
private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
return bubbles.mapNotNull { b ->
var shortcutId = b.shortcutInfo?.id
if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
if (shortcutId == null) return@mapNotNull null
BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight,
b.rawDesiredHeightResId)
BubbleEntity(
userId,
b.packageName,
b.shortcutInfo?.id ?: return@mapNotNull null,
b.key,
b.rawDesiredHeight,
b.rawDesiredHeightResId,
b.title
)
}
}
@@ -159,8 +163,13 @@ internal class BubbleDataRepository @Inject constructor(
val bubbles = entities.mapNotNull { entity ->
shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight,
entity.desiredHeightResId) }
?.let { shortcutInfo -> Bubble(
entity.key,
shortcutInfo,
entity.desiredHeight,
entity.desiredHeightResId,
entity.title
) }
}
uiScope.launch { cb(bubbles) }
}

View File

@@ -169,7 +169,7 @@ public class BubbleExpandedView extends LinearLayout {
return;
}
try {
if (!mIsOverflow && mBubble.usingShortcutInfo()) {
if (!mIsOverflow && mBubble.getShortcutInfo() != null) {
options.setApplyActivityFlagsForBubbles(true);
mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
options, null /* sourceBounds */);

View File

@@ -31,6 +31,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -223,9 +224,10 @@ public class BubbleFlyoutView extends FrameLayout {
float[] dotCenter,
boolean hideDot) {
if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
final Drawable senderAvatar = flyoutMessage.senderAvatar;
if (senderAvatar != null && flyoutMessage.isGroupChat) {
mSenderAvatar.setVisibility(VISIBLE);
mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar);
mSenderAvatar.setImageDrawable(senderAvatar);
} else {
mSenderAvatar.setVisibility(GONE);
mSenderAvatar.setTranslationX(0);

View File

@@ -15,7 +15,8 @@
*/
package com.android.systemui.bubbles;
import android.app.Notification;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -50,15 +51,14 @@ public class BubbleIconFactory extends BaseIconFactory {
/**
* Returns the drawable that the developer has provided to display in the bubble.
*/
Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo,
Notification.BubbleMetadata metadata) {
Drawable getBubbleDrawable(@NonNull final Context context,
@Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
if (shortcutInfo != null) {
LauncherApps launcherApps =
(LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
int density = context.getResources().getConfiguration().densityDpi;
return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
} else {
Icon ic = metadata.getIcon();
if (ic != null) {
if (ic.getType() == Icon.TYPE_URI
|| ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {

View File

@@ -16,8 +16,6 @@
package com.android.systemui.bubbles;
import android.service.notification.StatusBarNotification;
import com.android.internal.logging.UiEventLoggerImpl;
/**
@@ -32,12 +30,11 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger
* @param e UI event
*/
public void log(Bubble b, UiEventEnum e) {
if (b.getEntry() == null) {
if (b.getInstanceId() == null) {
// Added from persistence -- TODO log this with specific event?
return;
}
StatusBarNotification sbn = b.getEntry().getSbn();
logWithInstanceId(e, sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId());
logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
}
/**

View File

@@ -300,9 +300,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
});
// If the bubble was persisted, the entry is null but it should have shortcut info
ShortcutInfo info = b.getEntry() == null
? b.getShortcutInfo()
: b.getEntry().getRanking().getShortcutInfo();
ShortcutInfo info = b.getShortcutInfo();
if (info == null) {
Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b);
} else {

View File

@@ -91,7 +91,6 @@ import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
@@ -287,7 +286,7 @@ public class BubbleStackView extends FrameLayout
private BubbleController.BubbleExpandListener mExpandListener;
/** Callback to run when we want to unbubble the given notification's conversation. */
private Consumer<NotificationEntry> mUnbubbleConversationCallback;
private Consumer<String> mUnbubbleConversationCallback;
private SysUiState mSysUiState;
@@ -997,10 +996,7 @@ public class BubbleStackView extends FrameLayout
mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
mUnbubbleConversationCallback.accept(bubble.getEntry());
}
mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
});
mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
@@ -1348,7 +1344,7 @@ public class BubbleStackView extends FrameLayout
/** Sets the function to call to un-bubble the given conversation. */
public void setUnbubbleConversationCallback(
Consumer<NotificationEntry> unbubbleConversationCallback) {
Consumer<String> unbubbleConversationCallback) {
mUnbubbleConversationCallback = unbubbleConversationCallback;
}

View File

@@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;
import android.util.PathParser;
@@ -53,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Objects;
/**
* Simple task to inflate views & load necessary info to display a bubble.
@@ -129,35 +128,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Nullable
static BubbleViewInfo populate(Context c, BubbleStackView stackView,
BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
final NotificationEntry entry = b.getEntry();
if (entry == null) {
// populate from ShortcutInfo when NotificationEntry is not available
final ShortcutInfo s = b.getShortcutInfo();
return populate(c, stackView, iconFactory, skipInflation || b.isInflated(),
s.getPackage(), s.getUserHandle(), s, null);
}
final StatusBarNotification sbn = entry.getSbn();
final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId();
final ShortcutInfo si = bubbleShortcutId == null
? null : entry.getRanking().getShortcutInfo();
return populate(
c, stackView, iconFactory, skipInflation || b.isInflated(),
sbn.getPackageName(), sbn.getUser(), si, entry);
}
private static BubbleViewInfo populate(
@NonNull final Context c,
@NonNull final BubbleStackView stackView,
@NonNull final BubbleIconFactory iconFactory,
final boolean isInflated,
@NonNull final String packageName,
@NonNull final UserHandle user,
@Nullable final ShortcutInfo shortcutInfo,
@Nullable final NotificationEntry entry) {
BubbleViewInfo info = new BubbleViewInfo();
// View inflation: only should do this once per bubble
if (!isInflated) {
if (!skipInflation && !b.isInflated()) {
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
@@ -167,8 +141,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
info.expandedView.setStackView(stackView);
}
if (shortcutInfo != null) {
info.shortcutInfo = shortcutInfo;
if (b.getShortcutInfo() != null) {
info.shortcutInfo = b.getShortcutInfo();
}
// App name & app icon
@@ -178,7 +152,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Drawable appIcon;
try {
appInfo = pm.getApplicationInfo(
packageName,
b.getPackageName(),
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -186,17 +160,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
if (appInfo != null) {
info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
}
appIcon = pm.getApplicationIcon(packageName);
badgedIcon = pm.getUserBadgedIcon(appIcon, user);
appIcon = pm.getApplicationIcon(b.getPackageName());
badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
} catch (PackageManager.NameNotFoundException exception) {
// If we can't find package... don't think we should show the bubble.
Log.w(TAG, "Unable to find package: " + packageName);
Log.w(TAG, "Unable to find package: " + b.getPackageName());
return null;
}
// Badged bubble image
Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
entry == null ? null : entry.getBubbleMetadata());
b.getIcon());
if (bubbleDrawable == null) {
// Default to app icon
bubbleDrawable = appIcon;
@@ -222,8 +196,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Color.WHITE, WHITE_SCRIM_ALPHA);
// Flyout
if (entry != null) {
info.flyoutMessage = extractFlyoutMessage(c, entry);
info.flyoutMessage = b.getFlyoutMessage();
if (info.flyoutMessage != null) {
info.flyoutMessage.senderAvatar =
loadSenderAvatar(c, info.flyoutMessage.senderIcon);
}
return info;
}
@@ -235,8 +211,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
* notification, based on its type. Returns null if there should not be an update message.
*/
@NonNull
static Bubble.FlyoutMessage extractFlyoutMessage(Context context,
NotificationEntry entry) {
static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) {
Objects.requireNonNull(entry);
final Notification underlyingNotif = entry.getSbn().getNotification();
final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
@@ -264,20 +240,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
if (latestMessage != null) {
bubbleMessage.message = latestMessage.getText();
Person sender = latestMessage.getSenderPerson();
bubbleMessage.senderName = sender != null
? sender.getName()
: null;
bubbleMessage.senderName = sender != null ? sender.getName() : null;
bubbleMessage.senderAvatar = null;
if (sender != null && sender.getIcon() != null) {
if (sender.getIcon().getType() == Icon.TYPE_URI
|| sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
context.grantUriPermission(context.getPackageName(),
sender.getIcon().getUri(),
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context);
}
bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
return bubbleMessage;
}
} else if (Notification.InboxStyle.class.equals(style)) {
@@ -306,4 +271,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
return bubbleMessage;
}
@Nullable
static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
Objects.requireNonNull(context);
if (icon == null) return null;
if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
context.grantUriPermission(context.getPackageName(),
icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
return icon.loadDrawable(context);
}
}

View File

@@ -24,5 +24,6 @@ data class BubbleEntity(
val shortcutId: String,
val key: String,
val desiredHeight: Int,
@DimenRes val desiredHeightResId: Int
@DimenRes val desiredHeightResId: Int,
val title: String? = null
)

View File

@@ -33,6 +33,7 @@ private const val ATTR_SHORTCUT_ID = "sid"
private const val ATTR_KEY = "key"
private const val ATTR_DESIRED_HEIGHT = "h"
private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid"
private const val ATTR_TITLE = "t"
/**
* Writes the bubbles in xml format into given output stream.
@@ -63,6 +64,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
serializer.attribute(null, ATTR_KEY, bubble.key)
serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString())
serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString())
bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -92,7 +94,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null,
parser.getAttributeWithName(ATTR_KEY) ?: return null,
parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null
parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_TITLE)
)
}

View File

@@ -304,6 +304,10 @@ public class BubbleControllerTest extends SysuiTestCase {
public void testPromoteBubble_autoExpand() throws Exception {
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey()))
.thenReturn(mRow.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey()))
.thenReturn(mRow2.getEntry());
mBubbleController.removeBubble(
mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
@@ -331,6 +335,10 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */
false, /* showInShade */ true);
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey()))
.thenReturn(mRow.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey()))
.thenReturn(mRow2.getEntry());
mBubbleController.removeBubble(
mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
@@ -433,15 +441,16 @@ public class BubbleControllerTest extends SysuiTestCase {
assertTrue(mSysUiStateBubblesExpanded);
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow2.getEntry()));
// Switch which bubble is expanded
mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(
mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry()));
@@ -543,27 +552,27 @@ public class BubbleControllerTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow2.getEntry()));
// Dismiss currently expanded
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
.getEntry().getKey(),
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
// Make sure first bubble is selected
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
// Dismiss that one
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
.getEntry().getKey(),
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -839,6 +848,12 @@ public class BubbleControllerTest extends SysuiTestCase {
mRow2.getEntry(), /* suppressFlyout */ false, /* showInShade */ false);
mBubbleController.updateBubble(
mRow3.getEntry(), /* suppressFlyout */ false, /* showInShade */ false);
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey()))
.thenReturn(mRow.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey()))
.thenReturn(mRow2.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey()))
.thenReturn(mRow3.getEntry());
assertEquals(mBubbleData.getBubbles().size(), 3);
mBubbleData.setMaxOverflowBubbles(1);
@@ -908,6 +923,8 @@ public class BubbleControllerTest extends SysuiTestCase {
// GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -927,6 +944,8 @@ public class BubbleControllerTest extends SysuiTestCase {
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -948,6 +967,8 @@ public class BubbleControllerTest extends SysuiTestCase {
// GIVEN a group summary with two (non-bubble) children and one bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);

View File

@@ -86,8 +86,7 @@ public class BubbleTest extends SysuiTestCase {
final String msg = "Hello there!";
doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext,
mEntry).message);
assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
}
@Test
@@ -98,8 +97,7 @@ public class BubbleTest extends SysuiTestCase {
mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
// Should be big text, not the small text.
assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext,
mEntry).message);
assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
}
@Test
@@ -107,8 +105,7 @@ public class BubbleTest extends SysuiTestCase {
doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
// Media notifs don't get update messages.
assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext,
mEntry).message);
assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
}
@Test
@@ -124,7 +121,7 @@ public class BubbleTest extends SysuiTestCase {
// Should be the last one only.
assertEquals("Really? I prefer them that way.",
BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message);
BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
}
@Test
@@ -139,11 +136,8 @@ public class BubbleTest extends SysuiTestCase {
"Oh, hello!", 0, "Mady").toBundle()});
// Should be the last one only.
assertEquals("Oh, hello!",
BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message);
assertEquals("Mady",
BubbleViewInfoTask.extractFlyoutMessage(mContext,
mEntry).senderName);
assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName);
}
@Test

View File

@@ -302,6 +302,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
public void testRemoveBubble_withDismissedNotif_notInOverflow() {
mEntryListener.onEntryAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey()))
.thenReturn(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
@@ -388,14 +390,14 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
true, mRow.getEntry().getKey());
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
// Switch which bubble is expanded
mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
mBubbleData.setExpanded(true);
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry()));
@@ -488,27 +490,27 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow2.getEntry()));
// Dismiss currently expanded
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getEntry().getKey(),
stackView.getExpandedBubble().getKey()).getKey(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
// Make sure first bubble is selected
assertEquals(mRow.getEntry(),
mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
// Dismiss that one
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getEntry().getKey(),
stackView.getExpandedBubble().getKey()).getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -767,6 +769,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -785,6 +789,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -807,6 +813,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
// WHEN the summary is dismissed

View File

@@ -32,7 +32,7 @@ class BubblePersistentRepositoryTest : SysuiTestCase() {
private val bubbles = listOf(
BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0),
BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428),
BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0)
)
private lateinit var repository: BubblePersistentRepository

View File

@@ -37,9 +37,10 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() {
private val user0 = UserHandle.of(0)
private val user10 = UserHandle.of(10)
private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0)
private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428)
private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0)
private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0)
private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob",
"key-2", 0, 16537428, "title")
private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0)
private val bubbles = listOf(bubble1, bubble2, bubble3)

View File

@@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream
class BubbleXmlHelperTest : SysuiTestCase() {
private val bubbles = listOf(
BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0),
BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0)
BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0),
BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0)
)
@Test
fun testWriteXml() {
val expectedEntries = """
<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" />
<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" />
<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
""".trimIndent()
ByteArrayOutputStream().use {
writeXml(it, bubbles)
@@ -54,12 +54,12 @@ class BubbleXmlHelperTest : SysuiTestCase() {
@Test
fun testReadXml() {
val src = """
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<bs>
<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" />
<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
</bs>
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<bs>
<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" />
<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" />
<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" />
</bs>
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual)