diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 5b852086fe9f1..529af2297ef1a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; +import android.app.Person; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -35,16 +36,18 @@ import android.graphics.Bitmap; import android.graphics.Path; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Parcelable; import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; import java.util.Objects; /** @@ -169,11 +172,10 @@ class Bubble implements BubbleViewProvider { } @VisibleForTesting(visibility = PRIVATE) - Bubble(@NonNull final NotificationEntry e, + Bubble(@NonNull final BubbleEntry entry, @Nullable final BubbleController.NotificationSuppressionChangedListener listener, final BubbleController.PendingIntentCanceledListener intentCancelListener) { - Objects.requireNonNull(e); - mKey = e.getKey(); + mKey = entry.getKey(); mSuppressionListener = listener; mIntentCancelListener = intent -> { if (mIntent != null) { @@ -181,7 +183,7 @@ class Bubble implements BubbleViewProvider { } intentCancelListener.onPendingIntentCanceled(this); }; - setEntry(e); + setEntry(entry); } @Override @@ -294,8 +296,8 @@ class Bubble implements BubbleViewProvider { } /** - * Sets whether this bubble is considered visually interruptive. Normally pulled from the - * {@link NotificationEntry}, this method is purely for testing. + * Sets whether this bubble is considered visually interruptive. This method is purely for + * testing. */ @VisibleForTesting void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) { @@ -388,30 +390,28 @@ class Bubble implements BubbleViewProvider { /** * Sets the entry associated with this bubble. */ - void setEntry(@NonNull final NotificationEntry entry) { + void setEntry(@NonNull final BubbleEntry entry) { Objects.requireNonNull(entry); - Objects.requireNonNull(entry.getSbn()); - mLastUpdated = entry.getSbn().getPostTime(); - mIsBubble = entry.getSbn().getNotification().isBubbleNotification(); - mPackageName = entry.getSbn().getPackageName(); - mUser = entry.getSbn().getUser(); + mLastUpdated = entry.getStatusBarNotification().getPostTime(); + mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification(); + mPackageName = entry.getStatusBarNotification().getPackageName(); + mUser = entry.getStatusBarNotification().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); - mShortcutInfo = (entry.getRanking() != null ? entry.getRanking().getShortcutInfo() : null); - mMetadataShortcutId = (entry.getBubbleMetadata() != null - ? entry.getBubbleMetadata().getShortcutId() : null); + mChannelId = entry.getStatusBarNotification().getNotification().getChannelId(); + mNotificationId = entry.getStatusBarNotification().getId(); + mAppUid = entry.getStatusBarNotification().getUid(); + mInstanceId = entry.getStatusBarNotification().getInstanceId(); + mFlyoutMessage = extractFlyoutMessage(entry); if (entry.getRanking() != null) { + mShortcutInfo = entry.getRanking().getShortcutInfo(); mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + if (entry.getRanking().getChannel() != null) { + mIsImportantConversation = + entry.getRanking().getChannel().isImportantConversation(); + } } if (entry.getBubbleMetadata() != null) { + mMetadataShortcutId = entry.getBubbleMetadata().getShortcutId(); mFlags = entry.getBubbleMetadata().getFlags(); mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); @@ -433,8 +433,11 @@ class Bubble implements BubbleViewProvider { } mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } - mIsImportantConversation = - entry.getChannel() != null && entry.getChannel().isImportantConversation(); + + mIsClearable = entry.isClearable(); + mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); + mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); + mShouldSuppressPeek = entry.shouldSuppressPeek(); } @Nullable @@ -735,9 +738,75 @@ class Bubble implements BubbleViewProvider { } @Nullable - private static String getTitle(@NonNull final NotificationEntry e) { - final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); + private static String getTitle(@NonNull final BubbleEntry e) { + final CharSequence titleCharSeq = e.getStatusBarNotification() + .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE); return titleCharSeq == null ? null : titleCharSeq.toString(); } + + /** + * Returns our best guess for the most relevant text summary of the latest update to this + * notification, based on its type. Returns null if there should not be an update message. + */ + @NonNull + static Bubble.FlyoutMessage extractFlyoutMessage(BubbleEntry entry) { + Objects.requireNonNull(entry); + final Notification underlyingNotif = entry.getStatusBarNotification().getNotification(); + final Class style = underlyingNotif.getNotificationStyle(); + + Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage(); + bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean( + Notification.EXTRA_IS_GROUP_CONVERSATION); + try { + if (Notification.BigTextStyle.class.equals(style)) { + // Return the big text, it is big so probably important. If it's not there use the + // normal text. + CharSequence bigText = + underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT); + bubbleMessage.message = !TextUtils.isEmpty(bigText) + ? bigText + : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); + return bubbleMessage; + } else if (Notification.MessagingStyle.class.equals(style)) { + final List messages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray( + (Parcelable[]) underlyingNotif.extras.get( + Notification.EXTRA_MESSAGES)); + + final Notification.MessagingStyle.Message latestMessage = + Notification.MessagingStyle.findLatestIncomingMessage(messages); + if (latestMessage != null) { + bubbleMessage.message = latestMessage.getText(); + Person sender = latestMessage.getSenderPerson(); + bubbleMessage.senderName = sender != null ? sender.getName() : null; + bubbleMessage.senderAvatar = null; + bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; + return bubbleMessage; + } + } else if (Notification.InboxStyle.class.equals(style)) { + CharSequence[] lines = + underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); + + // Return the last line since it should be the most recent. + if (lines != null && lines.length > 0) { + bubbleMessage.message = lines[lines.length - 1]; + return bubbleMessage; + } + } else if (Notification.MediaStyle.class.equals(style)) { + // Return nothing, media updates aren't typically useful as a text update. + return bubbleMessage; + } else { + // Default to text extra. + bubbleMessage.message = + underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); + return bubbleMessage; + } + } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) { + // No use crashing, we'll just return null and the caller will assume there's no update + // message. + e.printStackTrace(); + } + + return bubbleMessage; + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 90b64eaf5b473..d4780f4b7da14 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -345,7 +345,42 @@ public class BubbleController implements Bubbles, ConfigurationController.Config /** * Injected constructor. See {@link BubbleModule}. */ - public BubbleController(Context context, + public static BubbleController create(Context context, + NotificationShadeWindowController notificationShadeWindowController, + StatusBarStateController statusBarStateController, + ShadeController shadeController, + @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, + ConfigurationController configurationController, + NotificationInterruptStateProvider interruptionStateProvider, + ZenModeController zenModeController, + NotificationLockscreenUserManager notifUserManager, + NotificationGroupManagerLegacy groupManager, + NotificationEntryManager entryManager, + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpManager dumpManager, + FloatingContentCoordinator floatingContentCoordinator, + BubbleDataRepository dataRepository, + SysUiState sysUiState, + INotificationManager notificationManager, + @Nullable IStatusBarService statusBarService, + WindowManager windowManager, + WindowManagerShellWrapper windowManagerShellWrapper, + LauncherApps launcherApps) { + return new BubbleController(context, notificationShadeWindowController, + statusBarStateController, shadeController, new BubbleData(context), synchronizer, + configurationController, interruptionStateProvider, zenModeController, + notifUserManager, groupManager, entryManager, notifPipeline, featureFlags, + dumpManager, floatingContentCoordinator, dataRepository, sysUiState, + notificationManager, statusBarService, windowManager, windowManagerShellWrapper, + launcherApps); + } + + /** + * Testing constructor. + */ + @VisibleForTesting + BubbleController(Context context, NotificationShadeWindowController notificationShadeWindowController, StatusBarStateController statusBarStateController, ShadeController shadeController, @@ -1120,9 +1155,10 @@ public class BubbleController implements Bubbles, ConfigurationController.Config && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); - b.setEntry(notif); + b.setEntry(notifToBubbleEntry(notif)); } else { - Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); + Bubble bubble = mBubbleData.getOrCreateBubble( + notifToBubbleEntry(notif), null /* persistedBubble */); inflateAndAdd(bubble, suppressFlyout, showInShade); } } @@ -1208,8 +1244,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config mBubbleData.removeSuppressedSummary(groupKey); // Remove any associated bubble children with the summary - final List bubbleChildren = mBubbleData.getBubblesInGroup( - groupKey, mNotificationEntryManager); + final List bubbleChildren = getBubblesInGroup(groupKey); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } @@ -1255,6 +1290,25 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } } + /** + * Retrieves any bubbles that are part of the notification group represented by the provided + * group key. + */ + private ArrayList getBubblesInGroup(@Nullable String groupKey) { + ArrayList bubbleChildren = new ArrayList<>(); + if (groupKey == null) { + return bubbleChildren; + } + for (Bubble bubble : mBubbleData.getActiveBubbles()) { + final NotificationEntry entry = + mNotificationEntryManager.getPendingOrActiveNotif(bubble.getKey()); + if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { + bubbleChildren.add(bubble); + } + } + return bubbleChildren; + } + private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble, final boolean autoExpand) { Objects.requireNonNull(entry); @@ -1365,8 +1419,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } if (entry != null) { final String groupKey = entry.getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup( - groupKey, mNotificationEntryManager).isEmpty()) { + if (getBubblesInGroup(groupKey).isEmpty()) { // Time to potentially remove the summary for (NotifCallback cb : mCallbacks) { cb.maybeCancelSummary(entry); @@ -1449,8 +1502,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } String groupKey = entry.getSbn().getGroupKey(); - ArrayList bubbleChildren = mBubbleData.getBubblesInGroup( - groupKey, mNotificationEntryManager); + ArrayList bubbleChildren = getBubblesInGroup(groupKey); boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); @@ -1694,4 +1746,10 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } } } + + static BubbleEntry notifToBubbleEntry(NotificationEntry e) { + return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(), + e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(), + e.shouldSuppressPeek()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 2c3cb5f8dcb09..55ecb226e3cb1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -36,8 +36,6 @@ import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -52,8 +50,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; -import javax.inject.Inject; - /** * Keeps track of active bubbles. */ @@ -154,12 +150,11 @@ public class BubbleData { * associated with it). This list is used to check if the summary should be hidden from the * shade. * - * Key: group key of the NotificationEntry - * Value: key of the NotificationEntry + * Key: group key of the notification + * Value: key of the notification */ private HashMap mSuppressedGroupKeys = new HashMap<>(); - @Inject public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); @@ -205,6 +200,11 @@ public class BubbleData { return mSelectedBubble; } + /** Return a read-only current active bubble lists. */ + public List getActiveBubbles() { + return Collections.unmodifiableList(mBubbles); + } + public void setExpanded(boolean expanded) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "setExpanded: " + expanded); @@ -235,8 +235,8 @@ public class BubbleData { * @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from * the overflow that was persisted over reboot. */ - Bubble getOrCreateBubble(NotificationEntry entry, Bubble persistedBubble) { - String key = entry != null ? entry.getKey() : persistedBubble.getKey(); + public Bubble getOrCreateBubble(BubbleEntry entry, Bubble persistedBubble) { + String key = persistedBubble != null ? persistedBubble.getKey() : entry.getKey(); Bubble bubbleToReturn = getBubbleInStackWithKey(key); if (bubbleToReturn == null) { @@ -266,7 +266,7 @@ public class BubbleData { /** * When this method is called it is expected that all info in the bubble has completed loading. * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, - * BubbleStackView, BubbleIconFactory). + * BubbleStackView, BubbleIconFactory, boolean). */ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) { if (DEBUG_BUBBLE_DATA) { @@ -329,7 +329,7 @@ public class BubbleData { * Retrieves the notif entry key of the summary associated with the provided group key. * * @param groupKey the group to look up - * @return the key for the {@link NotificationEntry} that is the summary of this group. + * @return the key for the notification that is the summary of this group. */ String getSummaryKey(String groupKey) { return mSuppressedGroupKeys.get(groupKey); @@ -349,25 +349,6 @@ public class BubbleData { return mSuppressedGroupKeys.containsKey(groupKey); } - /** - * Retrieves any bubbles that are part of the notification group represented by the provided - * group key. - */ - ArrayList getBubblesInGroup(@Nullable String groupKey, @NonNull - NotificationEntryManager nem) { - ArrayList bubbleChildren = new ArrayList<>(); - if (groupKey == null) { - return bubbleChildren; - } - for (Bubble b : mBubbles) { - final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey()); - if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { - bubbleChildren.add(b); - } - } - return bubbleChildren; - } - /** * Removes bubbles from the given package whose shortcut are not in the provided list of valid * shortcuts. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java new file mode 100644 index 0000000000000..6a13025186996 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java @@ -0,0 +1,98 @@ +/* + * 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.systemui.bubbles; + +import android.app.Notification.BubbleMetadata; +import android.app.NotificationManager.Policy; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.StatusBarNotification; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Represents a notification with needed data and flag for bubbles. + * + * @see Bubble + */ +public class BubbleEntry { + + private StatusBarNotification mSbn; + private Ranking mRanking; + + private boolean mIsClearable; + private boolean mShouldSuppressNotificationDot; + private boolean mShouldSuppressNotificationList; + private boolean mShouldSuppressPeek; + + public BubbleEntry(@NonNull StatusBarNotification sbn, + Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot, + boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) { + mSbn = sbn; + mRanking = ranking; + + mIsClearable = isClearable; + mShouldSuppressNotificationDot = shouldSuppressNotificationDot; + mShouldSuppressNotificationList = shouldSuppressNotificationList; + mShouldSuppressPeek = shouldSuppressPeek; + } + + /** @return the {@link StatusBarNotification} for this entry. */ + @NonNull + public StatusBarNotification getStatusBarNotification() { + return mSbn; + } + + /** @return the {@link Ranking} for this entry. */ + public Ranking getRanking() { + return mRanking; + } + + /** @return the key in the {@link StatusBarNotification}. */ + public String getKey() { + return mSbn.getKey(); + } + + /** @return the {@link BubbleMetadata} in the {@link StatusBarNotification}. */ + @Nullable + public BubbleMetadata getBubbleMetadata() { + return getStatusBarNotification().getNotification().getBubbleMetadata(); + } + + /** @return true if this notification is clearable. */ + public boolean isClearable() { + return mIsClearable; + } + + /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */ + public boolean shouldSuppressNotificationDot() { + return mShouldSuppressNotificationDot; + } + + /** + * @return true if {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST} + * set for this notification. + */ + public boolean shouldSuppressNotificationList() { + return mShouldSuppressNotificationList; + } + + /** @return true if {@link Policy#SUPPRESSED_EFFECT_PEEK} set for this notification. */ + public boolean shouldSuppressPeek() { + return mShouldSuppressPeek; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 28757facc2206..010a29e3560a3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -22,8 +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.Person; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -36,8 +34,6 @@ import android.graphics.Path; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; -import android.os.Parcelable; -import android.text.TextUtils; import android.util.Log; import android.util.PathParser; import android.view.LayoutInflater; @@ -47,10 +43,8 @@ import androidx.annotation.Nullable; import com.android.internal.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.ref.WeakReference; -import java.util.List; import java.util.Objects; /** @@ -208,73 +202,6 @@ public class BubbleViewInfoTask extends AsyncTask style = underlyingNotif.getNotificationStyle(); - - Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage(); - bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean( - Notification.EXTRA_IS_GROUP_CONVERSATION); - try { - if (Notification.BigTextStyle.class.equals(style)) { - // Return the big text, it is big so probably important. If it's not there use the - // normal text. - CharSequence bigText = - underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT); - bubbleMessage.message = !TextUtils.isEmpty(bigText) - ? bigText - : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); - return bubbleMessage; - } else if (Notification.MessagingStyle.class.equals(style)) { - final List messages = - Notification.MessagingStyle.Message.getMessagesFromBundleArray( - (Parcelable[]) underlyingNotif.extras.get( - Notification.EXTRA_MESSAGES)); - - final Notification.MessagingStyle.Message latestMessage = - Notification.MessagingStyle.findLatestIncomingMessage(messages); - if (latestMessage != null) { - bubbleMessage.message = latestMessage.getText(); - Person sender = latestMessage.getSenderPerson(); - bubbleMessage.senderName = sender != null ? sender.getName() : null; - bubbleMessage.senderAvatar = null; - bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; - return bubbleMessage; - } - } else if (Notification.InboxStyle.class.equals(style)) { - CharSequence[] lines = - underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); - - // Return the last line since it should be the most recent. - if (lines != null && lines.length > 0) { - bubbleMessage.message = lines[lines.length - 1]; - return bubbleMessage; - } - } else if (Notification.MediaStyle.class.equals(style)) { - // Return nothing, media updates aren't typically useful as a text update. - return bubbleMessage; - } else { - // Default to text extra. - bubbleMessage.message = - underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); - return bubbleMessage; - } - } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) { - // No use crashing, we'll just return null and the caller will assume there's no update - // message. - e.printStackTrace(); - } - - return bubbleMessage; - } - @Nullable static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { Objects.requireNonNull(context); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index d2eaf0d8d810c..00ae3a33b468d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -23,7 +23,6 @@ import android.view.WindowManager; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; -import com.android.systemui.bubbles.BubbleData; import com.android.systemui.bubbles.BubbleDataRepository; import com.android.systemui.bubbles.Bubbles; import com.android.systemui.dagger.SysUISingleton; @@ -59,7 +58,6 @@ public interface BubbleModule { NotificationShadeWindowController notificationShadeWindowController, StatusBarStateController statusBarStateController, ShadeController shadeController, - BubbleData data, ConfigurationController configurationController, NotificationInterruptStateProvider interruptionStateProvider, ZenModeController zenModeController, @@ -77,12 +75,11 @@ public interface BubbleModule { WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps) { - return new BubbleController( + return BubbleController.create( context, notificationShadeWindowController, statusBarStateController, shadeController, - data, null /* synchronizer */, configurationController, interruptionStateProvider, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 4bbc41e517b0f..6e2c7e5c19efd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -16,8 +16,6 @@ package com.android.systemui.bubbles; -import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -29,7 +27,9 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -39,10 +39,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleData.TimeSource; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.google.common.collect.ImmutableList; @@ -68,15 +64,15 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class BubbleDataTest extends SysuiTestCase { - private NotificationEntry mEntryA1; - private NotificationEntry mEntryA2; - private NotificationEntry mEntryA3; - private NotificationEntry mEntryB1; - private NotificationEntry mEntryB2; - private NotificationEntry mEntryB3; - private NotificationEntry mEntryC1; - private NotificationEntry mEntryInterruptive; - private NotificationEntry mEntryDismissed; + private BubbleEntry mEntryA1; + private BubbleEntry mEntryA2; + private BubbleEntry mEntryA3; + private BubbleEntry mEntryB1; + private BubbleEntry mEntryB2; + private BubbleEntry mEntryB3; + private BubbleEntry mEntryC1; + private BubbleEntry mEntryInterruptive; + private BubbleEntry mEntryDismissed; private Bubble mBubbleA1; private Bubble mBubbleA2; @@ -99,8 +95,6 @@ public class BubbleDataTest extends SysuiTestCase { @Mock private PendingIntent mDeleteIntent; - private NotificationTestHelper mNotificationTestHelper; - @Captor private ArgumentCaptor mUpdateCaptor; @@ -112,29 +106,23 @@ public class BubbleDataTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mNotificationTestHelper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); MockitoAnnotations.initMocks(this); - mEntryA1 = createBubbleEntry(1, "a1", "package.a"); - mEntryA2 = createBubbleEntry(1, "a2", "package.a"); - mEntryA3 = createBubbleEntry(1, "a3", "package.a"); - mEntryB1 = createBubbleEntry(1, "b1", "package.b"); - mEntryB2 = createBubbleEntry(1, "b2", "package.b"); - mEntryB3 = createBubbleEntry(1, "b3", "package.b"); - mEntryC1 = createBubbleEntry(1, "c1", "package.c"); + mEntryA1 = createBubbleEntry(1, "a1", "package.a", null); + mEntryA2 = createBubbleEntry(1, "a2", "package.a", null); + mEntryA3 = createBubbleEntry(1, "a3", "package.a", null); + mEntryB1 = createBubbleEntry(1, "b1", "package.b", null); + mEntryB2 = createBubbleEntry(1, "b2", "package.b", null); + mEntryB3 = createBubbleEntry(1, "b3", "package.b", null); + mEntryC1 = createBubbleEntry(1, "c1", "package.c", null); - mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d"); - modifyRanking(mEntryInterruptive) - .setVisuallyInterruptive(true) - .build(); + NotificationListenerService.Ranking ranking = + mock(NotificationListenerService.Ranking.class); + when(ranking.visuallyInterruptive()).thenReturn(true); + mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null); - ExpandableNotificationRow row = mNotificationTestHelper.createBubble(); - mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d"); - mEntryDismissed.setRow(row); + mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null); mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null); mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener); @@ -862,12 +850,13 @@ public class BubbleDataTest extends SysuiTestCase { } - private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) { - return createBubbleEntry(userId, notifKey, packageName, 1000); + private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName, + NotificationListenerService.Ranking ranking) { + return createBubbleEntry(userId, notifKey, packageName, ranking, 1000); } - private void setPostTime(NotificationEntry entry, long postTime) { - when(entry.getSbn().getPostTime()).thenReturn(postTime); + private void setPostTime(BubbleEntry entry, long postTime) { + when(entry.getStatusBarNotification().getPostTime()).thenReturn(postTime); } /** @@ -875,16 +864,19 @@ public class BubbleDataTest extends SysuiTestCase { * required for BubbleData functionality and verification. NotificationTestHelper is used only * as a convenience to create a Notification w/BubbleMetadata. */ - private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName, - long postTime) { + private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName, + NotificationListenerService.Ranking ranking, long postTime) { // BubbleMetadata Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder( mExpandIntent, Icon.createWithResource("", 0)) .setDeleteIntent(mDeleteIntent) .build(); // Notification -> BubbleMetadata - Notification notification = mNotificationTestHelper.createNotification(false, - null /* groupKey */, bubbleMetadata); + Notification notification = mock(Notification.class); + notification.setBubbleMetadata(bubbleMetadata); + + // Notification -> extras + notification.extras = new Bundle(); // StatusBarNotification StatusBarNotification sbn = mock(StatusBarNotification.class); @@ -895,18 +887,18 @@ public class BubbleDataTest extends SysuiTestCase { when(sbn.getNotification()).thenReturn(notification); // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata - return new NotificationEntryBuilder().setSbn(sbn).build(); + return new BubbleEntry(sbn, ranking, true, false, false, false); } private void setCurrentTime(long time) { when(mTimeSource.currentTimeMillis()).thenReturn(time); } - private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) { + private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) { sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */); } - private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime, + private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, boolean visuallyInterruptive) { setPostTime(entry, postTime); // BubbleController calls this: diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index f7f3a377f31d7..29ead593c51a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -24,12 +24,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.Bundle; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -37,8 +39,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import org.junit.Before; import org.junit.Test; @@ -52,8 +52,10 @@ import org.mockito.MockitoAnnotations; public class BubbleTest extends SysuiTestCase { @Mock private Notification mNotif; + @Mock + private StatusBarNotification mSbn; - private NotificationEntry mEntry; + private BubbleEntry mBubbleEntry; private Bundle mExtras; private Bubble mBubble; @@ -67,18 +69,16 @@ public class BubbleTest extends SysuiTestCase { mExtras = new Bundle(); mNotif.extras = mExtras; - mEntry = new NotificationEntryBuilder() - .setNotification(mNotif) - .build(); - - mBubble = new Bubble(mEntry, mSuppressionListener, null); - Intent target = new Intent(mContext, BubblesTestActivity.class); Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, target, 0), Icon.createWithResource(mContext, R.drawable.android)) .build(); - mEntry.setBubbleMetadata(metadata); + when(mSbn.getNotification()).thenReturn(mNotif); + when(mNotif.getBubbleMetadata()).thenReturn(metadata); + when(mSbn.getKey()).thenReturn("mock"); + mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false); + mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null); } @Test @@ -86,7 +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(mEntry).message); + assertEquals(msg, Bubble.extractFlyoutMessage(mBubbleEntry).message); } @Test @@ -97,7 +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(mEntry).message); + assertEquals(msg, Bubble.extractFlyoutMessage(mBubbleEntry).message); } @Test @@ -105,7 +105,7 @@ public class BubbleTest extends SysuiTestCase { doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); // Media notifs don't get update messages. - assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + assertNull(Bubble.extractFlyoutMessage(mBubbleEntry).message); } @Test @@ -121,7 +121,7 @@ public class BubbleTest extends SysuiTestCase { // Should be the last one only. assertEquals("Really? I prefer them that way.", - BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + Bubble.extractFlyoutMessage(mBubbleEntry).message); } @Test @@ -136,8 +136,8 @@ public class BubbleTest extends SysuiTestCase { "Oh, hello!", 0, "Mady").toBundle()}); // Should be the last one only. - assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); - assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName); + assertEquals("Oh, hello!", Bubble.extractFlyoutMessage(mBubbleEntry).message); + assertEquals("Mady", Bubble.extractFlyoutMessage(mBubbleEntry).senderName); } @Test