Merge "Migrate Bubbles to wm-shell (2/n)"
This commit is contained in:
committed by
Android (Google) Code Review
commit
94f3df4610
@@ -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<? extends Notification.Style> 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<Notification.MessagingStyle.Message> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
|
||||
groupKey, mNotificationEntryManager);
|
||||
final List<Bubble> 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<Bubble> getBubblesInGroup(@Nullable String groupKey) {
|
||||
ArrayList<Bubble> 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<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
|
||||
groupKey, mNotificationEntryManager);
|
||||
ArrayList<Bubble> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String> 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<Bubble> 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<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull
|
||||
NotificationEntryManager nem) {
|
||||
ArrayList<Bubble> 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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void, Void, BubbleViewInfoTask
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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(NotificationEntry entry) {
|
||||
Objects.requireNonNull(entry);
|
||||
final Notification underlyingNotif = entry.getSbn().getNotification();
|
||||
final Class<? extends Notification.Style> 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<Notification.MessagingStyle.Message> 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<BubbleData.Update> 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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user