Merge changes Iecbd8999,Ic2d5860e,If1b69c19 into rvc-dev
* changes: Move icon logic out of NotificationEntry Add getAllNotifs() to CommonNotifCollection Change NotifCollection to use an event queue
This commit is contained in:
@@ -33,6 +33,7 @@ import com.android.keyguard.AlphaOptimizedLinearLayout;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -159,20 +160,30 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
|
||||
}
|
||||
|
||||
public void setEntry(NotificationEntry entry) {
|
||||
if (entry != null) {
|
||||
mShowingEntry = entry;
|
||||
if (mShowingEntry != null) {
|
||||
mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
|
||||
}
|
||||
mShowingEntry = entry;
|
||||
|
||||
if (mShowingEntry != null) {
|
||||
CharSequence text = entry.headsUpStatusBarText;
|
||||
if (entry.isSensitive()) {
|
||||
text = entry.headsUpStatusBarTextPublic;
|
||||
}
|
||||
mTextView.setText(text);
|
||||
mShowingEntry.setOnSensitiveChangedListener(() -> setEntry(entry));
|
||||
} else if (mShowingEntry != null){
|
||||
mShowingEntry.setOnSensitiveChangedListener(null);
|
||||
mShowingEntry = null;
|
||||
mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
|
||||
}
|
||||
}
|
||||
|
||||
private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
|
||||
if (entry != mShowingEntry) {
|
||||
throw new IllegalStateException("Got a sensitivity change for " + entry
|
||||
+ " but mShowingEntry is " + mShowingEntry);
|
||||
}
|
||||
// Update the text
|
||||
setEntry(entry);
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
|
||||
@@ -70,6 +70,7 @@ import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -262,11 +263,11 @@ public class NotificationMediaManager implements Dumpable {
|
||||
synchronized (mEntryManager) {
|
||||
NotificationEntry entry = mEntryManager
|
||||
.getActiveNotificationUnfiltered(mMediaNotificationKey);
|
||||
if (entry == null || entry.expandedIcon == null) {
|
||||
if (entry == null || entry.getIcons().getShelfIcon() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.expandedIcon.getSourceIcon();
|
||||
return entry.getIcons().getShelfIcon().getSourceIcon();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,8 +285,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
boolean metaDataChanged = false;
|
||||
|
||||
synchronized (mEntryManager) {
|
||||
Set<NotificationEntry> allNotifications =
|
||||
mEntryManager.getPendingAndActiveNotifications();
|
||||
Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
|
||||
|
||||
// Promote the media notification with a controller in 'playing' state, if any.
|
||||
NotificationEntry mediaNotification = null;
|
||||
|
||||
@@ -329,7 +329,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
expandableRow.setAboveShelf(false);
|
||||
}
|
||||
if (notGoneIndex == 0) {
|
||||
StatusBarIconView icon = expandableRow.getEntry().expandedIcon;
|
||||
StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
|
||||
NotificationIconContainer.IconState iconState = getIconState(icon);
|
||||
// The icon state might be null in rare cases where the notification is actually
|
||||
// added to the layout, but not to the shelf. An example are replied messages,
|
||||
@@ -432,7 +432,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
// if the shelf is clipped, lets make sure we also clip the icon
|
||||
maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
|
||||
}
|
||||
StatusBarIconView icon = row.getEntry().expandedIcon;
|
||||
StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
|
||||
float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
|
||||
if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) {
|
||||
int top = (int) (maxTop - shelfIconPosition);
|
||||
@@ -444,7 +444,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
}
|
||||
|
||||
private void updateContinuousClipping(final ExpandableNotificationRow row) {
|
||||
StatusBarIconView icon = row.getEntry().expandedIcon;
|
||||
StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
|
||||
boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing();
|
||||
boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
|
||||
if (needsContinuousClipping && !isContinuousClipping) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.NotificationListenerService.RankingMap;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -56,9 +57,9 @@ import com.android.systemui.util.leak.LeakDetector;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -105,6 +106,10 @@ public class NotificationEntryManager implements
|
||||
*/
|
||||
public static final int UNDEFINED_DISMISS_REASON = 0;
|
||||
|
||||
private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
|
||||
private final Set<NotificationEntry> mReadOnlyAllNotifications =
|
||||
Collections.unmodifiableSet(mAllNotifications);
|
||||
|
||||
/** Pending notifications are ones awaiting inflation */
|
||||
@VisibleForTesting
|
||||
protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
|
||||
@@ -468,6 +473,8 @@ public class NotificationEntryManager implements
|
||||
entry.removeRow();
|
||||
}
|
||||
|
||||
mAllNotifications.remove(entry);
|
||||
|
||||
// Let's remove the children if this was a summary
|
||||
handleGroupSummaryRemoved(key);
|
||||
removeVisibleNotification(key);
|
||||
@@ -548,6 +555,7 @@ public class NotificationEntryManager implements
|
||||
notification,
|
||||
ranking,
|
||||
mFgsFeatureController.isForegroundServiceDismissalEnabled());
|
||||
mAllNotifications.add(entry);
|
||||
|
||||
mLeakDetector.trackInstance(entry);
|
||||
|
||||
@@ -708,15 +716,6 @@ public class NotificationEntryManager implements
|
||||
return mPendingNotifications.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all notifications we're currently aware of (both pending and active notifications)
|
||||
*/
|
||||
public Set<NotificationEntry> getPendingAndActiveNotifications() {
|
||||
Set<NotificationEntry> allNotifs = new HashSet<>(mPendingNotifications.values());
|
||||
allNotifs.addAll(mSortedAndFiltered);
|
||||
return allNotifs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to retrieve a notification entry that has been prepared for presentation.
|
||||
* Note that the notification may be filtered out and never shown to the user.
|
||||
@@ -842,7 +841,7 @@ public class NotificationEntryManager implements
|
||||
|
||||
private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
|
||||
pw.print(indent);
|
||||
pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.icon);
|
||||
pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.getIcons().getStatusBarIcon());
|
||||
StatusBarNotification n = e.getSbn();
|
||||
pw.print(indent);
|
||||
pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
|
||||
@@ -861,6 +860,15 @@ public class NotificationEntryManager implements
|
||||
return mReadOnlyNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collections containing ALL notifications we know about, including ones that are
|
||||
* hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
|
||||
*/
|
||||
@Override
|
||||
public Collection<NotificationEntry> getAllNotifs() {
|
||||
return mReadOnlyAllNotifications;
|
||||
}
|
||||
|
||||
/** @return A count of the active notifications */
|
||||
public int getActiveNotificationsCount() {
|
||||
return mReadOnlyNotifications.size();
|
||||
|
||||
@@ -64,24 +64,34 @@ import com.android.systemui.statusbar.FeatureFlags;
|
||||
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
|
||||
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CleanUpEntryEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryAddedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
|
||||
import com.android.systemui.util.Assert;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -124,6 +134,8 @@ public class NotifCollection implements Dumpable {
|
||||
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
|
||||
private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
|
||||
|
||||
private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
|
||||
|
||||
private boolean mAttached = false;
|
||||
private boolean mAmDispatchingToOtherCode;
|
||||
|
||||
@@ -160,8 +172,8 @@ public class NotifCollection implements Dumpable {
|
||||
mBuildListener = buildListener;
|
||||
}
|
||||
|
||||
/** @see NotifPipeline#getActiveNotifs() */
|
||||
Collection<NotificationEntry> getActiveNotifs() {
|
||||
/** @see NotifPipeline#getAllNotifs() */
|
||||
Collection<NotificationEntry> getAllNotifs() {
|
||||
Assert.isMainThread();
|
||||
return mReadOnlyNotificationSet;
|
||||
}
|
||||
@@ -242,7 +254,7 @@ public class NotifCollection implements Dumpable {
|
||||
}
|
||||
|
||||
locallyDismissNotifications(entriesToLocallyDismiss);
|
||||
rebuildList();
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,8 +263,7 @@ public class NotifCollection implements Dumpable {
|
||||
public void dismissNotification(
|
||||
NotificationEntry entry,
|
||||
@NonNull DismissedByUserStats stats) {
|
||||
dismissNotifications(List.of(
|
||||
new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
|
||||
dismissNotifications(List.of(new Pair<>(entry, stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,7 +279,7 @@ public class NotifCollection implements Dumpable {
|
||||
// system process is dead if we're here.
|
||||
}
|
||||
|
||||
final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
|
||||
final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
|
||||
for (int i = entries.size() - 1; i >= 0; i--) {
|
||||
NotificationEntry entry = entries.get(i);
|
||||
if (!shouldDismissOnClearAll(entry, userId)) {
|
||||
@@ -283,7 +294,7 @@ public class NotifCollection implements Dumpable {
|
||||
}
|
||||
|
||||
locallyDismissNotifications(entries);
|
||||
rebuildList();
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,8 +337,9 @@ public class NotifCollection implements Dumpable {
|
||||
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
|
||||
Assert.isMainThread();
|
||||
|
||||
postNotification(sbn, requireRanking(rankingMap, sbn.getKey()), rankingMap);
|
||||
rebuildList();
|
||||
postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
|
||||
applyRanking(rankingMap);
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
|
||||
@@ -336,9 +348,9 @@ public class NotifCollection implements Dumpable {
|
||||
mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size());
|
||||
|
||||
for (CoalescedEvent event : batch) {
|
||||
postNotification(event.getSbn(), event.getRanking(), null);
|
||||
postNotification(event.getSbn(), event.getRanking());
|
||||
}
|
||||
rebuildList();
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
private void onNotificationRemoved(
|
||||
@@ -354,55 +366,49 @@ public class NotifCollection implements Dumpable {
|
||||
throw new IllegalStateException("No notification to remove with key " + sbn.getKey());
|
||||
}
|
||||
entry.mCancellationReason = reason;
|
||||
applyRanking(rankingMap);
|
||||
tryRemoveNotification(entry);
|
||||
rebuildList();
|
||||
applyRanking(rankingMap);
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
private void onNotificationRankingUpdate(RankingMap rankingMap) {
|
||||
Assert.isMainThread();
|
||||
mEventQueue.add(new RankingUpdatedEvent(rankingMap));
|
||||
applyRanking(rankingMap);
|
||||
dispatchNotificationRankingUpdate(rankingMap);
|
||||
rebuildList();
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
private void postNotification(
|
||||
StatusBarNotification sbn,
|
||||
Ranking ranking,
|
||||
@Nullable RankingMap rankingMap) {
|
||||
Ranking ranking) {
|
||||
NotificationEntry entry = mNotificationSet.get(sbn.getKey());
|
||||
|
||||
if (entry == null) {
|
||||
// A new notification!
|
||||
mLogger.logNotifPosted(sbn.getKey());
|
||||
|
||||
entry = new NotificationEntry(sbn, ranking);
|
||||
mNotificationSet.put(sbn.getKey(), entry);
|
||||
dispatchOnEntryInit(entry);
|
||||
|
||||
if (rankingMap != null) {
|
||||
applyRanking(rankingMap);
|
||||
}
|
||||
|
||||
dispatchOnEntryAdded(entry);
|
||||
mLogger.logNotifPosted(sbn.getKey());
|
||||
mEventQueue.add(new InitEntryEvent(entry));
|
||||
mEventQueue.add(new EntryAddedEvent(entry));
|
||||
|
||||
} else {
|
||||
// Update to an existing entry
|
||||
mLogger.logNotifUpdated(sbn.getKey());
|
||||
|
||||
// Notification is updated so it is essentially re-added and thus alive again, so we
|
||||
// can reset its state.
|
||||
// TODO: If a coalesced event ever gets here, it's possible to lose track of children,
|
||||
// since their rankings might have been updated earlier (and thus we may no longer
|
||||
// think a child is associated with this locally-dismissed entry).
|
||||
cancelLocalDismissal(entry);
|
||||
cancelLifetimeExtension(entry);
|
||||
cancelDismissInterception(entry);
|
||||
entry.mCancellationReason = REASON_NOT_CANCELED;
|
||||
|
||||
entry.setSbn(sbn);
|
||||
if (rankingMap != null) {
|
||||
applyRanking(rankingMap);
|
||||
}
|
||||
|
||||
dispatchOnEntryUpdated(entry);
|
||||
mLogger.logNotifUpdated(sbn.getKey());
|
||||
mEventQueue.add(new EntryUpdatedEvent(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,8 +438,8 @@ public class NotifCollection implements Dumpable {
|
||||
if (!isLifetimeExtended(entry)) {
|
||||
mNotificationSet.remove(entry.getKey());
|
||||
cancelDismissInterception(entry);
|
||||
dispatchOnEntryRemoved(entry, entry.mCancellationReason);
|
||||
dispatchOnEntryCleanUp(entry);
|
||||
mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason));
|
||||
mEventQueue.add(new CleanUpEntryEvent(entry));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -466,9 +472,16 @@ public class NotifCollection implements Dumpable {
|
||||
}
|
||||
}
|
||||
}
|
||||
mEventQueue.add(new RankingAppliedEvent());
|
||||
}
|
||||
|
||||
private void rebuildList() {
|
||||
private void dispatchEventsAndRebuildList() {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
while (!mEventQueue.isEmpty()) {
|
||||
mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
|
||||
if (mBuildListener != null) {
|
||||
mBuildListener.onBuildList(mReadOnlyNotificationSet);
|
||||
}
|
||||
@@ -491,7 +504,7 @@ public class NotifCollection implements Dumpable {
|
||||
|
||||
if (!isLifetimeExtended(entry)) {
|
||||
if (tryRemoveNotification(entry)) {
|
||||
rebuildList();
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -660,57 +673,9 @@ public class NotifCollection implements Dumpable {
|
||||
|| entry.getSbn().getUser().getIdentifier() == userId;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryInit(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryInit(entry);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryAdded(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryAdded(entry);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryUpdated(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryUpdated(entry);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchNotificationRankingUpdate(RankingMap map) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onRankingUpdate(map);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryRemoved(entry, reason);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryCleanUp(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryCleanUp(entry);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
|
||||
final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
|
||||
final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
|
||||
|
||||
pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
|
||||
if (entries.size() == 0) {
|
||||
@@ -754,6 +719,7 @@ public class NotifCollection implements Dumpable {
|
||||
private static final String TAG = "NotifCollection";
|
||||
|
||||
@IntDef(prefix = { "REASON_" }, value = {
|
||||
REASON_NOT_CANCELED,
|
||||
REASON_UNKNOWN,
|
||||
REASON_CLICK,
|
||||
REASON_CANCEL_ALL,
|
||||
|
||||
@@ -82,14 +82,14 @@ public class NotifPipeline implements CommonNotifCollection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of "active" notifications, i.e. the notifications that are currently posted
|
||||
* Returns the list of all known notifications, i.e. the notifications that are currently posted
|
||||
* to the phone. In general, this tracks closely to the list maintained by NotificationManager,
|
||||
* but it can diverge slightly due to lifetime extenders.
|
||||
*
|
||||
* The returned collection is read-only, unsorted, unfiltered, and ungrouped.
|
||||
*/
|
||||
public Collection<NotificationEntry> getActiveNotifs() {
|
||||
return mNotifCollection.getActiveNotifs();
|
||||
public Collection<NotificationEntry> getAllNotifs() {
|
||||
return mNotifCollection.getAllNotifs();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,8 +29,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
|
||||
|
||||
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
|
||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
|
||||
@@ -44,10 +42,7 @@ import android.app.NotificationManager.Policy;
|
||||
import android.app.Person;
|
||||
import android.app.RemoteInputHistoryItem;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.LauncherApps.ShortcutQuery;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
@@ -55,25 +50,20 @@ import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.SnoozeCriterion;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.statusbar.StatusBarIcon;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.ContrastColorUtil;
|
||||
import com.android.systemui.statusbar.InflationTask;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.notification.InflationException;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
|
||||
import com.android.systemui.statusbar.notification.icon.IconPack;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGuts;
|
||||
@@ -81,8 +71,6 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -102,15 +90,11 @@ import java.util.Objects;
|
||||
* clean this up in the future.
|
||||
*/
|
||||
public final class NotificationEntry extends ListEntry {
|
||||
private static final String TAG = "NotificationEntry";
|
||||
|
||||
private final String mKey;
|
||||
private StatusBarNotification mSbn;
|
||||
private Ranking mRanking;
|
||||
|
||||
private StatusBarIcon mSmallIcon;
|
||||
private StatusBarIcon mPeopleAvatar;
|
||||
|
||||
/*
|
||||
* Bookkeeping members
|
||||
*/
|
||||
@@ -142,10 +126,7 @@ public final class NotificationEntry extends ListEntry {
|
||||
* TODO: Remove every member beneath this line if possible
|
||||
*/
|
||||
|
||||
public StatusBarIconView icon;
|
||||
public StatusBarIconView expandedIcon;
|
||||
public StatusBarIconView centeredIcon;
|
||||
public StatusBarIconView aodIcon;
|
||||
private IconPack mIcons = IconPack.buildEmptyPack(null);
|
||||
private boolean interruption;
|
||||
public int targetSdk;
|
||||
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
|
||||
@@ -191,7 +172,8 @@ public final class NotificationEntry extends ListEntry {
|
||||
private boolean hasSentReply;
|
||||
|
||||
private boolean mSensitive = true;
|
||||
private Runnable mOnSensitiveChangedListener;
|
||||
private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();
|
||||
|
||||
private boolean mAutoHeadsUp;
|
||||
private boolean mPulseSupressed;
|
||||
private boolean mAllowFgsDismissal;
|
||||
@@ -347,6 +329,15 @@ public final class NotificationEntry extends ListEntry {
|
||||
* TODO: Remove as many of these as possible
|
||||
*/
|
||||
|
||||
@NonNull
|
||||
public IconPack getIcons() {
|
||||
return mIcons;
|
||||
}
|
||||
|
||||
public void setIcons(@NonNull IconPack icons) {
|
||||
mIcons = icons;
|
||||
}
|
||||
|
||||
public void setInterruption() {
|
||||
interruption = true;
|
||||
}
|
||||
@@ -464,239 +455,6 @@ public final class NotificationEntry extends ListEntry {
|
||||
|| SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the icons for a notification
|
||||
* @param context the context to create the icons with
|
||||
* @param sbn the notification
|
||||
* @throws InflationException Exception if required icons are not valid or specified
|
||||
*/
|
||||
public void createIcons(Context context, StatusBarNotification sbn)
|
||||
throws InflationException {
|
||||
StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
|
||||
|
||||
// Construct the icon.
|
||||
icon = new StatusBarIconView(context,
|
||||
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
|
||||
icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
|
||||
// Construct the expanded icon.
|
||||
expandedIcon = new StatusBarIconView(context,
|
||||
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
|
||||
expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
|
||||
// Construct the expanded icon.
|
||||
aodIcon = new StatusBarIconView(context,
|
||||
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
|
||||
aodIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
aodIcon.setIncreasedSize(true);
|
||||
|
||||
try {
|
||||
setIcons(ic, Collections.singletonList(icon));
|
||||
if (isSensitive()) {
|
||||
ic = getIcon(context, sbn, true /* redact */);
|
||||
}
|
||||
setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
|
||||
} catch (InflationException e) {
|
||||
icon = null;
|
||||
expandedIcon = null;
|
||||
centeredIcon = null;
|
||||
aodIcon = null;
|
||||
throw e;
|
||||
}
|
||||
|
||||
expandedIcon.setVisibility(View.INVISIBLE);
|
||||
expandedIcon.setOnVisibilityChangedListener(
|
||||
newVisibility -> {
|
||||
if (row != null) {
|
||||
row.setIconsVisible(newVisibility != View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
// Construct the centered icon
|
||||
if (mSbn.getNotification().isMediaNotification()) {
|
||||
centeredIcon = new StatusBarIconView(context,
|
||||
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
|
||||
centeredIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
try {
|
||||
setIcons(ic, Collections.singletonList(centeredIcon));
|
||||
} catch (InflationException e) {
|
||||
centeredIcon = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this icon should be tinted based on the sensitivity of the icon, its context
|
||||
* and the user's indicated sensitivity preference.
|
||||
*
|
||||
* @param ic The icon that should/should not be tinted.
|
||||
* @return
|
||||
*/
|
||||
private boolean shouldTintIcon(StatusBarIconView ic) {
|
||||
boolean usedInSensitiveContext = (ic == expandedIcon || ic == aodIcon);
|
||||
return !isImportantConversation() || (usedInSensitiveContext && isSensitive());
|
||||
}
|
||||
|
||||
|
||||
private void setIcons(StatusBarIcon ic, List<StatusBarIconView> icons)
|
||||
throws InflationException {
|
||||
for (StatusBarIconView icon: icons) {
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
icon.setTintIcons(shouldTintIcon(icon));
|
||||
if (!icon.set(ic)) {
|
||||
throw new InflationException("Couldn't create icon" + ic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StatusBarIcon getIcon(Context context, StatusBarNotification sbn, boolean redact)
|
||||
throws InflationException {
|
||||
Notification n = sbn.getNotification();
|
||||
final boolean showPeopleAvatar = isImportantConversation() && !redact;
|
||||
|
||||
// If cached, return corresponding cached values
|
||||
if (showPeopleAvatar && mPeopleAvatar != null) {
|
||||
return mPeopleAvatar;
|
||||
} else if (!showPeopleAvatar && mSmallIcon != null) {
|
||||
return mSmallIcon;
|
||||
}
|
||||
|
||||
Icon icon = showPeopleAvatar ? createPeopleAvatar(context) : n.getSmallIcon();
|
||||
if (icon == null) {
|
||||
throw new InflationException("No icon in notification from " + sbn.getPackageName());
|
||||
}
|
||||
|
||||
StatusBarIcon ic = new StatusBarIcon(
|
||||
sbn.getUser(),
|
||||
sbn.getPackageName(),
|
||||
icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
StatusBarIconView.contentDescForNotification(context, n));
|
||||
|
||||
// Cache if important conversation.
|
||||
if (isImportantConversation()) {
|
||||
if (showPeopleAvatar) {
|
||||
mPeopleAvatar = ic;
|
||||
} else {
|
||||
mSmallIcon = ic;
|
||||
}
|
||||
}
|
||||
return ic;
|
||||
}
|
||||
|
||||
private Icon createPeopleAvatar(Context context) throws InflationException {
|
||||
// Attempt to extract form shortcut.
|
||||
String conversationId = getChannel().getConversationId();
|
||||
ShortcutQuery query = new ShortcutQuery()
|
||||
.setPackage(mSbn.getPackageName())
|
||||
.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
|
||||
.setShortcutIds(Collections.singletonList(conversationId));
|
||||
List<ShortcutInfo> shortcuts = context.getSystemService(LauncherApps.class)
|
||||
.getShortcuts(query, mSbn.getUser());
|
||||
Icon ic = null;
|
||||
if (shortcuts != null && !shortcuts.isEmpty()) {
|
||||
ic = shortcuts.get(0).getIcon();
|
||||
}
|
||||
|
||||
// Fall back to notification large icon if available
|
||||
if (ic == null) {
|
||||
ic = mSbn.getNotification().getLargeIcon();
|
||||
}
|
||||
|
||||
// Fall back to extract from message
|
||||
if (ic == null) {
|
||||
Bundle extras = mSbn.getNotification().extras;
|
||||
List<Message> messages = Message.getMessagesFromBundleArray(
|
||||
extras.getParcelableArray(Notification.EXTRA_MESSAGES));
|
||||
Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
|
||||
|
||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||
Message message = messages.get(i);
|
||||
Person sender = message.getSenderPerson();
|
||||
if (sender != null && sender != user) {
|
||||
ic = message.getSenderPerson().getIcon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Revert to small icon if still not available
|
||||
if (ic == null) {
|
||||
ic = mSbn.getNotification().getSmallIcon();
|
||||
}
|
||||
if (ic == null) {
|
||||
throw new InflationException("No icon in notification from " + mSbn.getPackageName());
|
||||
}
|
||||
return ic;
|
||||
}
|
||||
|
||||
private void updateSensitiveIconState() {
|
||||
try {
|
||||
StatusBarIcon ic = getIcon(getRow().getContext(), mSbn, isSensitive());
|
||||
setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
|
||||
} catch (InflationException e) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Unable to update icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setIconTag(int key, Object tag) {
|
||||
if (icon != null) {
|
||||
icon.setTag(key, tag);
|
||||
expandedIcon.setTag(key, tag);
|
||||
}
|
||||
|
||||
if (centeredIcon != null) {
|
||||
centeredIcon.setTag(key, tag);
|
||||
}
|
||||
|
||||
if (aodIcon != null) {
|
||||
aodIcon.setTag(key, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the notification icons.
|
||||
*
|
||||
* @param context the context to create the icons with.
|
||||
* @param sbn the notification to read the icon from.
|
||||
* @throws InflationException Exception if required icons are not valid or specified
|
||||
*/
|
||||
public void updateIcons(Context context, StatusBarNotification sbn)
|
||||
throws InflationException {
|
||||
if (icon != null) {
|
||||
// Update the icon
|
||||
mSmallIcon = null;
|
||||
mPeopleAvatar = null;
|
||||
|
||||
StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
|
||||
|
||||
icon.setNotification(sbn);
|
||||
expandedIcon.setNotification(sbn);
|
||||
aodIcon.setNotification(sbn);
|
||||
setIcons(ic, Arrays.asList(icon, expandedIcon));
|
||||
|
||||
if (isSensitive()) {
|
||||
ic = getIcon(context, sbn, true /* redact */);
|
||||
}
|
||||
setIcons(ic, Collections.singletonList(aodIcon));
|
||||
|
||||
if (centeredIcon != null) {
|
||||
centeredIcon.setNotification(sbn);
|
||||
setIcons(ic, Collections.singletonList(centeredIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isImportantConversation() {
|
||||
return getChannel() != null && getChannel().isImportantConversation();
|
||||
}
|
||||
|
||||
public int getContrastedColor(Context context, boolean isLowPriority,
|
||||
int backgroundColor) {
|
||||
int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
|
||||
@@ -1125,9 +883,8 @@ public final class NotificationEntry extends ListEntry {
|
||||
getRow().setSensitive(sensitive, deviceSensitive);
|
||||
if (sensitive != mSensitive) {
|
||||
mSensitive = sensitive;
|
||||
updateSensitiveIconState();
|
||||
if (mOnSensitiveChangedListener != null) {
|
||||
mOnSensitiveChangedListener.run();
|
||||
for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
|
||||
mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1136,8 +893,14 @@ public final class NotificationEntry extends ListEntry {
|
||||
return mSensitive;
|
||||
}
|
||||
|
||||
public void setOnSensitiveChangedListener(Runnable listener) {
|
||||
mOnSensitiveChangedListener = listener;
|
||||
/** Add a listener to be notified when the entry's sensitivity changes. */
|
||||
public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
|
||||
mOnSensitivityChangedListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Remove a listener that was registered above. */
|
||||
public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
|
||||
mOnSensitivityChangedListeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean isPulseSuppressed() {
|
||||
@@ -1167,6 +930,12 @@ public final class NotificationEntry extends ListEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener interface for {@link #addOnSensitivityChangedListener} */
|
||||
public interface OnSensitivityChangedListener {
|
||||
/** Called when the sensitivity changes */
|
||||
void onSensitivityChanged(@NonNull NotificationEntry entry);
|
||||
}
|
||||
|
||||
/** @see #getDismissState() */
|
||||
public enum DismissState {
|
||||
/** User has not dismissed this notif or its parent */
|
||||
|
||||
@@ -126,7 +126,7 @@ public class BubbleCoordinator implements Coordinator {
|
||||
mInterceptedDismissalEntries.remove(entry.getKey());
|
||||
mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
|
||||
createDismissedByUserStats(entry));
|
||||
} else if (mNotifPipeline.getActiveNotifs().contains(entry)) {
|
||||
} else if (mNotifPipeline.getAllNotifs().contains(entry)) {
|
||||
// Bubbles are hiding the notifications from the shade, but the bubble was
|
||||
// deleted; therefore, the notification should be cancelled as if it were a user
|
||||
// dismissal (this won't re-enter handleInterceptDimissal because Bubbles
|
||||
|
||||
@@ -240,7 +240,7 @@ public class ForegroundCoordinator implements Coordinator {
|
||||
}
|
||||
|
||||
private NotificationEntry findNotificationEntryWithKey(String key) {
|
||||
for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
|
||||
for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {
|
||||
if (entry.getKey().equals(key)) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification.collection.inflation;
|
||||
|
||||
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
|
||||
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -29,8 +28,6 @@ import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.internal.util.NotificationMessagingUtil;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
@@ -38,25 +35,22 @@ import com.android.systemui.statusbar.NotificationUiAdjustment;
|
||||
import com.android.systemui.statusbar.notification.InflationException;
|
||||
import com.android.systemui.statusbar.notification.NotificationClicker;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.icon.IconManager;
|
||||
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
|
||||
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
|
||||
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
|
||||
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
|
||||
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
|
||||
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -66,23 +60,23 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
|
||||
private static final String TAG = "NotificationViewManager";
|
||||
|
||||
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
|
||||
|
||||
private final Context mContext;
|
||||
private final NotifBindPipeline mNotifBindPipeline;
|
||||
private final RowContentBindStage mRowContentBindStage;
|
||||
private final NotificationMessagingUtil mMessagingUtil;
|
||||
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
|
||||
private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
|
||||
private final NotifBindPipeline mNotifBindPipeline;
|
||||
private final RowContentBindStage mRowContentBindStage;
|
||||
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
|
||||
private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
|
||||
private final ExpandableNotificationRowComponent.Builder
|
||||
mExpandableNotificationRowComponentBuilder;
|
||||
private final IconManager mIconManager;
|
||||
|
||||
private NotificationPresenter mPresenter;
|
||||
private NotificationListContainer mListContainer;
|
||||
private NotificationRowContentBinder.InflationCallback mInflationCallback;
|
||||
private BindRowCallback mBindRowCallback;
|
||||
private NotificationClicker mNotificationClicker;
|
||||
private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
|
||||
private final ExpandableNotificationRowComponent.Builder
|
||||
mExpandableNotificationRowComponentBuilder;
|
||||
|
||||
@Inject
|
||||
public NotificationRowBinderImpl(
|
||||
@@ -92,14 +86,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
NotificationLockscreenUserManager notificationLockscreenUserManager,
|
||||
NotifBindPipeline notifBindPipeline,
|
||||
RowContentBindStage rowContentBindStage,
|
||||
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
StatusBarStateController statusBarStateController,
|
||||
NotificationGroupManager notificationGroupManager,
|
||||
NotificationGutsManager notificationGutsManager,
|
||||
NotificationInterruptStateProvider notificationInterruptionStateProvider,
|
||||
Provider<RowInflaterTask> rowInflaterTaskProvider,
|
||||
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
|
||||
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
|
||||
IconManager iconManager) {
|
||||
mContext = context;
|
||||
mNotifBindPipeline = notifBindPipeline;
|
||||
mRowContentBindStage = rowContentBindStage;
|
||||
@@ -109,6 +99,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
|
||||
mRowInflaterTaskProvider = rowInflaterTaskProvider;
|
||||
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
|
||||
mIconManager = iconManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,6 +111,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
mPresenter = presenter;
|
||||
mListContainer = listContainer;
|
||||
mBindRowCallback = bindRowCallback;
|
||||
|
||||
mIconManager.attach();
|
||||
}
|
||||
|
||||
public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
|
||||
@@ -142,12 +135,12 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
|
||||
final StatusBarNotification sbn = entry.getSbn();
|
||||
if (entry.rowExists()) {
|
||||
entry.updateIcons(mContext, sbn);
|
||||
mIconManager.updateIcons(entry);
|
||||
entry.reset();
|
||||
updateNotification(entry, pmUser, sbn, entry.getRow());
|
||||
entry.getRowController().setOnDismissRunnable(onDismissRunnable);
|
||||
} else {
|
||||
entry.createIcons(mContext, sbn);
|
||||
mIconManager.createIcons(entry);
|
||||
mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
|
||||
row -> {
|
||||
// Setup the controller for the view.
|
||||
@@ -227,8 +220,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
|
||||
row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
|
||||
&& entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
|
||||
|
||||
// TODO: should updates to the entry be happening somewhere else?
|
||||
entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
|
||||
// TODO: should this be happening somewhere else?
|
||||
mIconManager.updateIconTags(entry, entry.targetSdk);
|
||||
|
||||
row.setOnActivatedListener(mPresenter);
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A notification collection that manages the list of {@link NotificationEntry}s that will be
|
||||
* rendered.
|
||||
@@ -34,4 +36,13 @@ public interface CommonNotifCollection {
|
||||
* or deleted.
|
||||
*/
|
||||
void addCollectionListener(NotifCollectionListener listener);
|
||||
|
||||
/**
|
||||
* Returns the list of all known notifications, i.e. the notifications that are currently posted
|
||||
* to the phone. In general, this tracks closely to the list maintained by NotificationManager,
|
||||
* but it can diverge slightly due to lifetime extenders.
|
||||
*
|
||||
* The returned collection is read-only, unsorted, unfiltered, and ungrouped.
|
||||
*/
|
||||
Collection<NotificationEntry> getAllNotifs();
|
||||
}
|
||||
|
||||
@@ -70,8 +70,24 @@ public interface NotifCollectionListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the RankingMap is updated by system server. By the time this listener is
|
||||
* called, the Rankings of all entries will have been updated.
|
||||
* Called whenever a ranking update is applied. During a ranking update, all active,
|
||||
* non-lifetime-extended notification entries will have their ranking object updated.
|
||||
*
|
||||
* Ranking updates occur whenever a notification is added, updated, or removed, or when a
|
||||
* standalone ranking is sent from the server.
|
||||
*/
|
||||
default void onRankingApplied() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever system server sends a standalone ranking update (i.e. one that isn't
|
||||
* associated with a notification being added or removed).
|
||||
*
|
||||
* In general it is unsafe to depend on this method as rankings can change for other reasons.
|
||||
* Instead, listen for {@link #onRankingApplied()}, which is called whenever ANY ranking update
|
||||
* is applied, regardless of source.
|
||||
*
|
||||
* @deprecated Use {@link #onRankingApplied()} instead.
|
||||
*/
|
||||
default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.statusbar.notification.collection.notifcollection
|
||||
|
||||
import android.service.notification.NotificationListenerService.RankingMap
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
|
||||
/**
|
||||
* Set of classes that represent the various events that [NotifCollection] can dispatch to
|
||||
* [NotifCollectionListener]s.
|
||||
*
|
||||
* These events build up in a queue and are periodically emitted in chunks by the collection.
|
||||
*/
|
||||
|
||||
sealed class NotifEvent {
|
||||
fun dispatchTo(listeners: List<NotifCollectionListener>) {
|
||||
for (i in listeners.indices) {
|
||||
dispatchToListener(listeners[i])
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun dispatchToListener(listener: NotifCollectionListener)
|
||||
}
|
||||
|
||||
data class InitEntryEvent(
|
||||
val entry: NotificationEntry
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryInit(entry)
|
||||
}
|
||||
}
|
||||
|
||||
data class EntryAddedEvent(
|
||||
val entry: NotificationEntry
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryAdded(entry)
|
||||
}
|
||||
}
|
||||
|
||||
data class EntryUpdatedEvent(
|
||||
val entry: NotificationEntry
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryUpdated(entry)
|
||||
}
|
||||
}
|
||||
|
||||
data class EntryRemovedEvent(
|
||||
val entry: NotificationEntry,
|
||||
val reason: Int
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryRemoved(entry, reason)
|
||||
}
|
||||
}
|
||||
|
||||
data class CleanUpEntryEvent(
|
||||
val entry: NotificationEntry
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryCleanUp(entry)
|
||||
}
|
||||
}
|
||||
|
||||
data class RankingUpdatedEvent(
|
||||
val rankingMap: RankingMap
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onRankingUpdate(rankingMap)
|
||||
}
|
||||
}
|
||||
|
||||
class RankingAppliedEvent() : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onRankingApplied()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.statusbar.notification.icon
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Testable wrapper around Context.
|
||||
*/
|
||||
class IconBuilder @Inject constructor(
|
||||
private val context: Context
|
||||
) {
|
||||
fun createIconView(entry: NotificationEntry): StatusBarIconView {
|
||||
return StatusBarIconView(
|
||||
context,
|
||||
"${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
|
||||
entry.sbn)
|
||||
}
|
||||
|
||||
fun getIconContentDescription(n: Notification): CharSequence {
|
||||
return StatusBarIconView.contentDescForNotification(context, n)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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.statusbar.notification.icon
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Person
|
||||
import android.content.pm.LauncherApps
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.android.internal.statusbar.StatusBarIcon
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
import com.android.systemui.statusbar.notification.InflationException
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Inflates and updates icons associated with notifications
|
||||
*
|
||||
* Notifications are represented by icons in a few different places -- in the status bar, in the
|
||||
* notification shelf, in AOD, etc. This class is in charge of inflating the views that hold these
|
||||
* icons and keeping the icon assets themselves up to date as notifications change.
|
||||
*
|
||||
* TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
|
||||
* Long-term, it should probably live somewhere in the content inflation pipeline.
|
||||
*/
|
||||
class IconManager @Inject constructor(
|
||||
private val notifCollection: CommonNotifCollection,
|
||||
private val launcherApps: LauncherApps,
|
||||
private val iconBuilder: IconBuilder
|
||||
) {
|
||||
fun attach() {
|
||||
notifCollection.addCollectionListener(entryListener)
|
||||
}
|
||||
|
||||
private val entryListener = object : NotifCollectionListener {
|
||||
override fun onEntryInit(entry: NotificationEntry) {
|
||||
entry.addOnSensitivityChangedListener(sensitivityListener)
|
||||
}
|
||||
|
||||
override fun onEntryCleanUp(entry: NotificationEntry) {
|
||||
entry.removeOnSensitivityChangedListener(sensitivityListener)
|
||||
}
|
||||
|
||||
override fun onRankingApplied() {
|
||||
// When the sensitivity changes OR when the isImportantConversation status changes,
|
||||
// we need to update the icons
|
||||
for (entry in notifCollection.allNotifs) {
|
||||
val isImportant = isImportantConversation(entry)
|
||||
if (entry.icons.areIconsAvailable &&
|
||||
isImportant != entry.icons.isImportantConversation) {
|
||||
updateIconsSafe(entry)
|
||||
}
|
||||
entry.icons.isImportantConversation = isImportant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
|
||||
entry -> updateIconsSafe(entry)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
|
||||
* result in [NotificationEntry.getIcons].
|
||||
*
|
||||
* @throws InflationException Exception if required icons are not valid or specified
|
||||
*/
|
||||
@Throws(InflationException::class)
|
||||
fun createIcons(entry: NotificationEntry) {
|
||||
// Construct the status bar icon view.
|
||||
val sbIcon = iconBuilder.createIconView(entry)
|
||||
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
|
||||
// Construct the shelf icon view.
|
||||
val shelfIcon = iconBuilder.createIconView(entry)
|
||||
shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
|
||||
shelfIcon.visibility = View.INVISIBLE
|
||||
// TODO: This doesn't belong here
|
||||
shelfIcon.setOnVisibilityChangedListener { newVisibility: Int ->
|
||||
if (entry.row != null) {
|
||||
entry.row.setIconsVisible(newVisibility != View.VISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the aod icon view.
|
||||
val aodIcon = iconBuilder.createIconView(entry)
|
||||
aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
aodIcon.setIncreasedSize(true)
|
||||
|
||||
// Construct the centered icon view.
|
||||
val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
|
||||
iconBuilder.createIconView(entry).apply {
|
||||
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Set the icon views' icons
|
||||
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
|
||||
|
||||
try {
|
||||
setIcon(entry, normalIconDescriptor, sbIcon)
|
||||
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
|
||||
setIcon(entry, sensitiveIconDescriptor, aodIcon)
|
||||
if (centeredIcon != null) {
|
||||
setIcon(entry, normalIconDescriptor, centeredIcon)
|
||||
}
|
||||
entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
|
||||
} catch (e: InflationException) {
|
||||
entry.icons = IconPack.buildEmptyPack(entry.icons)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the notification icons.
|
||||
*
|
||||
* @param entry the notification to read the icon from.
|
||||
* @throws InflationException Exception if required icons are not valid or specified
|
||||
*/
|
||||
@Throws(InflationException::class)
|
||||
fun updateIcons(entry: NotificationEntry) {
|
||||
if (!entry.icons.areIconsAvailable) {
|
||||
return
|
||||
}
|
||||
entry.icons.smallIconDescriptor = null
|
||||
entry.icons.peopleAvatarDescriptor = null
|
||||
|
||||
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
|
||||
|
||||
entry.icons.statusBarIcon?.let {
|
||||
it.notification = entry.sbn
|
||||
setIcon(entry, normalIconDescriptor, it)
|
||||
}
|
||||
|
||||
entry.icons.shelfIcon?.let {
|
||||
it.notification = entry.sbn
|
||||
setIcon(entry, normalIconDescriptor, it)
|
||||
}
|
||||
|
||||
entry.icons.aodIcon?.let {
|
||||
it.notification = entry.sbn
|
||||
setIcon(entry, sensitiveIconDescriptor, it)
|
||||
}
|
||||
|
||||
entry.icons.centeredIcon?.let {
|
||||
it.notification = entry.sbn
|
||||
setIcon(entry, sensitiveIconDescriptor, it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates tags on the icon views to match the posting app's target SDK level
|
||||
*
|
||||
* Note that this method MUST be called after both [createIcons] and [updateIcons].
|
||||
*/
|
||||
fun updateIconTags(entry: NotificationEntry, targetSdk: Int) {
|
||||
setTagOnIconViews(
|
||||
entry.icons,
|
||||
R.id.icon_is_pre_L,
|
||||
targetSdk < Build.VERSION_CODES.LOLLIPOP)
|
||||
}
|
||||
|
||||
private fun updateIconsSafe(entry: NotificationEntry) {
|
||||
try {
|
||||
updateIcons(entry)
|
||||
} catch (e: InflationException) {
|
||||
// TODO This should mark the entire row as involved in an inflation error
|
||||
Log.e(TAG, "Unable to update icon", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(InflationException::class)
|
||||
private fun getIconDescriptors(
|
||||
entry: NotificationEntry
|
||||
): Pair<StatusBarIcon, StatusBarIcon> {
|
||||
val iconDescriptor = getIconDescriptor(entry, false /* redact */)
|
||||
val sensitiveDescriptor = if (entry.isSensitive) {
|
||||
getIconDescriptor(entry, true /* redact */)
|
||||
} else {
|
||||
iconDescriptor
|
||||
}
|
||||
return Pair(iconDescriptor, sensitiveDescriptor)
|
||||
}
|
||||
|
||||
@Throws(InflationException::class)
|
||||
private fun getIconDescriptor(
|
||||
entry: NotificationEntry,
|
||||
redact: Boolean
|
||||
): StatusBarIcon {
|
||||
val n = entry.sbn.notification
|
||||
val showPeopleAvatar = isImportantConversation(entry) && !redact
|
||||
|
||||
val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
|
||||
val smallIconDescriptor = entry.icons.smallIconDescriptor
|
||||
|
||||
// If cached, return corresponding cached values
|
||||
if (showPeopleAvatar && peopleAvatarDescriptor != null) {
|
||||
return peopleAvatarDescriptor
|
||||
} else if (!showPeopleAvatar && smallIconDescriptor != null) {
|
||||
return smallIconDescriptor
|
||||
}
|
||||
|
||||
val icon =
|
||||
(if (showPeopleAvatar) {
|
||||
createPeopleAvatar(entry)
|
||||
} else {
|
||||
n.smallIcon
|
||||
}) ?: throw InflationException(
|
||||
"No icon in notification from " + entry.sbn.packageName)
|
||||
|
||||
val ic = StatusBarIcon(
|
||||
entry.sbn.user,
|
||||
entry.sbn.packageName,
|
||||
icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
iconBuilder.getIconContentDescription(n))
|
||||
|
||||
// Cache if important conversation.
|
||||
if (isImportantConversation(entry)) {
|
||||
if (showPeopleAvatar) {
|
||||
entry.icons.peopleAvatarDescriptor = ic
|
||||
} else {
|
||||
entry.icons.smallIconDescriptor = ic
|
||||
}
|
||||
}
|
||||
|
||||
return ic
|
||||
}
|
||||
|
||||
@Throws(InflationException::class)
|
||||
private fun setIcon(
|
||||
entry: NotificationEntry,
|
||||
iconDescriptor: StatusBarIcon,
|
||||
iconView: StatusBarIconView
|
||||
) {
|
||||
iconView.setTintIcons(shouldTintIconView(entry, iconView))
|
||||
if (!iconView.set(iconDescriptor)) {
|
||||
throw InflationException("Couldn't create icon $iconDescriptor")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(InflationException::class)
|
||||
private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
|
||||
// Attempt to extract form shortcut.
|
||||
val conversationId = entry.ranking.channel.conversationId
|
||||
val query = LauncherApps.ShortcutQuery()
|
||||
.setPackage(entry.sbn.packageName)
|
||||
.setQueryFlags(
|
||||
LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
|
||||
or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
|
||||
.setShortcutIds(listOf(conversationId))
|
||||
val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user)
|
||||
var ic: Icon? = null
|
||||
if (shortcuts != null && shortcuts.isNotEmpty()) {
|
||||
ic = shortcuts[0].icon
|
||||
}
|
||||
|
||||
// Fall back to notification large icon if available
|
||||
if (ic == null) {
|
||||
ic = entry.sbn.notification.getLargeIcon()
|
||||
}
|
||||
|
||||
// Fall back to extract from message
|
||||
if (ic == null) {
|
||||
val extras: Bundle = entry.sbn.notification.extras
|
||||
val messages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(
|
||||
extras.getParcelableArray(Notification.EXTRA_MESSAGES))
|
||||
val user = extras.getParcelable<Person>(Notification.EXTRA_MESSAGING_PERSON)
|
||||
for (i in messages.indices.reversed()) {
|
||||
val message = messages[i]
|
||||
val sender = message.senderPerson
|
||||
if (sender != null && sender !== user) {
|
||||
ic = message.senderPerson!!.icon
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Revert to small icon if still not available
|
||||
if (ic == null) {
|
||||
ic = entry.sbn.notification.smallIcon
|
||||
}
|
||||
if (ic == null) {
|
||||
throw InflationException("No icon in notification from " + entry.sbn.packageName)
|
||||
}
|
||||
return ic
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this icon should be tinted based on the sensitivity of the icon, its context
|
||||
* and the user's indicated sensitivity preference.
|
||||
*
|
||||
* @param iconView The icon that should/should not be tinted.
|
||||
*/
|
||||
private fun shouldTintIconView(entry: NotificationEntry, iconView: StatusBarIconView): Boolean {
|
||||
val usedInSensitiveContext =
|
||||
iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
|
||||
return !isImportantConversation(entry) || usedInSensitiveContext && entry.isSensitive
|
||||
}
|
||||
|
||||
private fun isImportantConversation(entry: NotificationEntry): Boolean {
|
||||
return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
|
||||
}
|
||||
|
||||
private fun setTagOnIconViews(icons: IconPack, key: Int, tag: Any) {
|
||||
icons.statusBarIcon?.setTag(key, tag)
|
||||
icons.shelfIcon?.setTag(key, tag)
|
||||
icons.aodIcon?.setTag(key, tag)
|
||||
icons.centeredIcon?.setTag(key, tag)
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "IconManager"
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.statusbar.notification.icon;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.statusbar.StatusBarIcon;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
|
||||
/**
|
||||
* Data class for storing icons associated with a notification
|
||||
*/
|
||||
public final class IconPack {
|
||||
|
||||
private final boolean mAreIconsAvailable;
|
||||
@Nullable private final StatusBarIconView mStatusBarIcon;
|
||||
@Nullable private final StatusBarIconView mShelfIcon;
|
||||
@Nullable private final StatusBarIconView mAodIcon;
|
||||
@Nullable private final StatusBarIconView mCenteredIcon;
|
||||
|
||||
@Nullable private StatusBarIcon mSmallIconDescriptor;
|
||||
@Nullable private StatusBarIcon mPeopleAvatarDescriptor;
|
||||
|
||||
private boolean mIsImportantConversation;
|
||||
|
||||
/**
|
||||
* Builds an empty instance of IconPack that doesn't have any icons (because either they
|
||||
* haven't been inflated yet or there was an error while inflating them).
|
||||
*/
|
||||
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
|
||||
return new IconPack(false, null, null, null, null, fromSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of an IconPack that contains successfully-inflated icons
|
||||
*/
|
||||
public static IconPack buildPack(
|
||||
@NonNull StatusBarIconView statusBarIcon,
|
||||
@NonNull StatusBarIconView shelfIcon,
|
||||
@NonNull StatusBarIconView aodIcon,
|
||||
@Nullable StatusBarIconView centeredIcon,
|
||||
@Nullable IconPack source) {
|
||||
return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
|
||||
}
|
||||
|
||||
private IconPack(
|
||||
boolean areIconsAvailable,
|
||||
@Nullable StatusBarIconView statusBarIcon,
|
||||
@Nullable StatusBarIconView shelfIcon,
|
||||
@Nullable StatusBarIconView aodIcon,
|
||||
@Nullable StatusBarIconView centeredIcon,
|
||||
@Nullable IconPack source) {
|
||||
mAreIconsAvailable = areIconsAvailable;
|
||||
mStatusBarIcon = statusBarIcon;
|
||||
mShelfIcon = shelfIcon;
|
||||
mCenteredIcon = centeredIcon;
|
||||
mAodIcon = aodIcon;
|
||||
if (source != null) {
|
||||
mIsImportantConversation = source.mIsImportantConversation;
|
||||
}
|
||||
}
|
||||
|
||||
/** The version of the notification icon that appears in the status bar. */
|
||||
@Nullable
|
||||
public StatusBarIconView getStatusBarIcon() {
|
||||
return mStatusBarIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of the icon that appears in the "shelf" at the bottom of the notification shade.
|
||||
* In general, this icon also appears somewhere on the notification and is "sucked" into the
|
||||
* shelf as the scrolls beyond it.
|
||||
*/
|
||||
@Nullable
|
||||
public StatusBarIconView getShelfIcon() {
|
||||
return mShelfIcon;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StatusBarIconView getCenteredIcon() {
|
||||
return mCenteredIcon;
|
||||
}
|
||||
|
||||
/** The version of the icon that's shown when pulsing (in AOD). */
|
||||
@Nullable
|
||||
public StatusBarIconView getAodIcon() {
|
||||
return mAodIcon;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
StatusBarIcon getSmallIconDescriptor() {
|
||||
return mSmallIconDescriptor;
|
||||
}
|
||||
|
||||
void setSmallIconDescriptor(@Nullable StatusBarIcon smallIconDescriptor) {
|
||||
mSmallIconDescriptor = smallIconDescriptor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
StatusBarIcon getPeopleAvatarDescriptor() {
|
||||
return mPeopleAvatarDescriptor;
|
||||
}
|
||||
|
||||
void setPeopleAvatarDescriptor(@Nullable StatusBarIcon peopleAvatarDescriptor) {
|
||||
mPeopleAvatarDescriptor = peopleAvatarDescriptor;
|
||||
}
|
||||
|
||||
boolean isImportantConversation() {
|
||||
return mIsImportantConversation;
|
||||
}
|
||||
|
||||
void setImportantConversation(boolean importantConversation) {
|
||||
mIsImportantConversation = importantConversation;
|
||||
}
|
||||
|
||||
public boolean getAreIconsAvailable() {
|
||||
return mAreIconsAvailable;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class DungeonRow(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
||||
}
|
||||
|
||||
(findViewById(R.id.icon) as StatusBarIconView).apply {
|
||||
set(entry?.icon?.statusBarIcon)
|
||||
set(entry?.icons?.statusBarIcon?.statusBarIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +579,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
|
||||
@VisibleForTesting
|
||||
void updateShelfIconColor() {
|
||||
StatusBarIconView expandedIcon = mEntry.expandedIcon;
|
||||
StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
|
||||
boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
|
||||
boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
|
||||
ContrastColorUtil.getInstance(mContext));
|
||||
@@ -1290,7 +1290,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
setLongPressListener(null);
|
||||
mGroupParentWhenDismissed = mNotificationParent;
|
||||
mChildAfterViewWhenDismissed = null;
|
||||
mEntry.icon.setDismissed();
|
||||
mEntry.getIcons().getStatusBarIcon().setDismissed();
|
||||
if (isChildInGroup()) {
|
||||
List<ExpandableNotificationRow> notificationChildren =
|
||||
mNotificationParent.getNotificationChildren();
|
||||
@@ -1832,7 +1832,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
mTranslateableViews.get(i).setTranslationX(0);
|
||||
}
|
||||
invalidateOutline();
|
||||
getEntry().expandedIcon.setScrollX(0);
|
||||
getEntry().getIcons().getShelfIcon().setScrollX(0);
|
||||
}
|
||||
|
||||
if (mMenuRow != null) {
|
||||
@@ -1912,7 +1912,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
// In order to keep the shelf in sync with this swiping, we're simply translating
|
||||
// it's icon by the same amount. The translation is already being used for the normal
|
||||
// positioning, so we can use the scrollX instead.
|
||||
getEntry().expandedIcon.setScrollX((int) -translationX);
|
||||
getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
|
||||
}
|
||||
|
||||
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
|
||||
@@ -2111,7 +2111,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
|
||||
@Override
|
||||
public StatusBarIconView getShelfIcon() {
|
||||
return getEntry().expandedIcon;
|
||||
return getEntry().getIcons().getShelfIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -453,9 +453,10 @@ public class StackStateAnimator {
|
||||
needsAnimation = false;
|
||||
}
|
||||
NotificationEntry entry = row.getEntry();
|
||||
StatusBarIconView icon = entry.icon;
|
||||
if (entry.centeredIcon != null && entry.centeredIcon.getParent() != null) {
|
||||
icon = entry.centeredIcon;
|
||||
StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
|
||||
final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
|
||||
if (centeredIcon != null && centeredIcon.getParent() != null) {
|
||||
icon = centeredIcon;
|
||||
}
|
||||
if (icon.getParent() != null) {
|
||||
icon.getLocationOnScreen(mTmpLocation);
|
||||
|
||||
@@ -269,7 +269,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
|
||||
}
|
||||
updateIsolatedIconLocation(false /* requireUpdate */);
|
||||
mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
|
||||
: newEntry.icon, animateIsolation);
|
||||
: newEntry.getIcons().getStatusBarIcon(), animateIsolation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,8 +253,8 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
boolean hidePulsing, boolean onlyShowCenteredIcon) {
|
||||
|
||||
final boolean isCenteredNotificationIcon = mCenteredIconView != null
|
||||
&& entry.centeredIcon != null
|
||||
&& Objects.equals(entry.centeredIcon, mCenteredIconView);
|
||||
&& entry.getIcons().getCenteredIcon() != null
|
||||
&& Objects.equals(entry.getIcons().getCenteredIcon(), mCenteredIconView);
|
||||
if (onlyShowCenteredIcon) {
|
||||
return isCenteredNotificationIcon;
|
||||
}
|
||||
@@ -307,7 +307,7 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
}
|
||||
|
||||
private void updateShelfIcons() {
|
||||
updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
|
||||
updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
|
||||
true /* showAmbient */,
|
||||
true /* showLowPriority */,
|
||||
false /* hideDismissed */,
|
||||
@@ -319,7 +319,7 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
}
|
||||
|
||||
public void updateStatusBarIcons() {
|
||||
updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
|
||||
updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
|
||||
false /* showAmbient */,
|
||||
mShowLowPriority,
|
||||
true /* hideDismissed */,
|
||||
@@ -331,7 +331,7 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
}
|
||||
|
||||
private void updateCenterIcon() {
|
||||
updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon,
|
||||
updateIconsForLayout(entry -> entry.getIcons().getCenteredIcon(), mCenteredIcon,
|
||||
false /* showAmbient */,
|
||||
true /* showLowPriority */,
|
||||
false /* hideDismissed */,
|
||||
@@ -343,7 +343,7 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
}
|
||||
|
||||
public void updateAodNotificationIcons() {
|
||||
updateIconsForLayout(entry -> entry.aodIcon, mAodIcons,
|
||||
updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
|
||||
false /* showAmbient */,
|
||||
true /* showLowPriority */,
|
||||
true /* hideDismissed */,
|
||||
@@ -517,7 +517,7 @@ public class NotificationIconAreaController implements DarkReceiver,
|
||||
* Shows the icon view given in the center.
|
||||
*/
|
||||
public void showIconCentered(NotificationEntry entry) {
|
||||
StatusBarIconView icon = entry == null ? null : entry.centeredIcon;
|
||||
StatusBarIconView icon = entry == null ? null : entry.getIcons().getCenteredIcon();
|
||||
if (!Objects.equals(mCenteredIconView, icon)) {
|
||||
mCenteredIconView = icon;
|
||||
updateNotificationIcons();
|
||||
|
||||
@@ -34,16 +34,17 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -83,13 +84,13 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -123,6 +124,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
private NotifCollection mCollection;
|
||||
private BatchableNotificationHandler mNotifHandler;
|
||||
|
||||
private InOrder mListenerInOrder;
|
||||
|
||||
private NoManSimulator mNoMan;
|
||||
|
||||
@Before
|
||||
@@ -133,6 +136,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
|
||||
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true);
|
||||
|
||||
mListenerInOrder = inOrder(mCollectionListener);
|
||||
|
||||
mCollection = new NotifCollection(
|
||||
mStatusBarService,
|
||||
mock(DumpManager.class),
|
||||
@@ -159,10 +164,12 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
.setRank(4747));
|
||||
|
||||
// THEN the listener is notified
|
||||
verify(mCollectionListener).onEntryInit(mEntryCaptor.capture());
|
||||
NotificationEntry entry = mEntryCaptor.getValue();
|
||||
final NotificationEntry entry = mCollectionListener.getEntry(notif1.key);
|
||||
|
||||
mListenerInOrder.verify(mCollectionListener).onEntryInit(entry);
|
||||
mListenerInOrder.verify(mCollectionListener).onEntryAdded(entry);
|
||||
mListenerInOrder.verify(mCollectionListener).onRankingApplied();
|
||||
|
||||
verify(mCollectionListener).onEntryAdded(entry);
|
||||
assertEquals(notif1.key, entry.getKey());
|
||||
assertEquals(notif1.sbn, entry.getSbn());
|
||||
assertEquals(notif1.ranking, entry.getRanking());
|
||||
@@ -215,12 +222,11 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
assertEquals(entry2.getRanking(), capturedUpdate.getRanking());
|
||||
|
||||
// THEN onBuildList is called only once
|
||||
verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
|
||||
assertEquals(new ArraySet<>(Arrays.asList(
|
||||
capturedAdds.get(0),
|
||||
capturedAdds.get(1),
|
||||
capturedUpdate
|
||||
)), new ArraySet<>(mBuildListCaptor.getValue()));
|
||||
verifyBuiltList(
|
||||
List.of(
|
||||
capturedAdds.get(0),
|
||||
capturedAdds.get(1),
|
||||
capturedUpdate));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -234,9 +240,11 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
.setRank(89));
|
||||
|
||||
// THEN the listener is notified
|
||||
verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
|
||||
final NotificationEntry entry = mCollectionListener.getEntry(notif2.key);
|
||||
|
||||
mListenerInOrder.verify(mCollectionListener).onEntryUpdated(entry);
|
||||
mListenerInOrder.verify(mCollectionListener).onRankingApplied();
|
||||
|
||||
NotificationEntry entry = mEntryCaptor.getValue();
|
||||
assertEquals(notif2.key, entry.getKey());
|
||||
assertEquals(notif2.sbn, entry.getSbn());
|
||||
assertEquals(notif2.ranking, entry.getRanking());
|
||||
@@ -256,8 +264,10 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
|
||||
|
||||
// THEN the listener is notified
|
||||
verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
|
||||
verify(mCollectionListener).onEntryCleanUp(entry);
|
||||
mListenerInOrder.verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
|
||||
mListenerInOrder.verify(mCollectionListener).onEntryCleanUp(entry);
|
||||
mListenerInOrder.verify(mCollectionListener).onRankingApplied();
|
||||
|
||||
assertEquals(notif.sbn, entry.getSbn());
|
||||
assertEquals(notif.ranking, entry.getRanking());
|
||||
}
|
||||
@@ -415,8 +425,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// THEN the dismissed entry still appears in the notification set
|
||||
assertEquals(
|
||||
new ArraySet<>(Collections.singletonList(entry1)),
|
||||
new ArraySet<>(mCollection.getActiveNotifs()));
|
||||
new ArraySet<>(singletonList(entry1)),
|
||||
new ArraySet<>(mCollection.getAllNotifs()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -444,7 +454,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_CANCEL);
|
||||
assertEquals(
|
||||
new ArraySet<>(List.of(entry1, entry2, entry3)),
|
||||
new ArraySet<>(mCollection.getActiveNotifs()));
|
||||
new ArraySet<>(mCollection.getAllNotifs()));
|
||||
|
||||
// WHEN the summary is dismissed by the user
|
||||
mCollection.dismissNotification(entry1, defaultStats(entry1));
|
||||
@@ -452,7 +462,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
// THEN the summary is removed, but both children stick around
|
||||
assertEquals(
|
||||
new ArraySet<>(List.of(entry2, entry3)),
|
||||
new ArraySet<>(mCollection.getActiveNotifs()));
|
||||
new ArraySet<>(mCollection.getAllNotifs()));
|
||||
assertEquals(NOT_DISMISSED, entry2.getDismissState());
|
||||
assertEquals(NOT_DISMISSED, entry3.getDismissState());
|
||||
}
|
||||
@@ -561,7 +571,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException {
|
||||
public void testEndDismissInterceptionUpdatesDismissInterceptors() {
|
||||
// GIVEN a collection with notifications with multiple dismiss interceptors
|
||||
mInterceptor1.shouldInterceptDismissal = true;
|
||||
mInterceptor2.shouldInterceptDismissal = true;
|
||||
@@ -592,7 +602,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException {
|
||||
public void testEndingDismissalOfNonInterceptedThrows() {
|
||||
// GIVEN a collection with notifications with a dismiss interceptor that hasn't been called
|
||||
mInterceptor1.shouldInterceptDismissal = false;
|
||||
mCollection.addNotificationDismissInterceptor(mInterceptor1);
|
||||
@@ -820,7 +830,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK);
|
||||
|
||||
// THEN the entry is not removed
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
|
||||
// THEN the entry properly records all extenders that returned true
|
||||
assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
|
||||
@@ -841,7 +851,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by one of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN the last active extender expires (but new ones become active)
|
||||
@@ -856,7 +866,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
|
||||
|
||||
// THEN the entry is not removed
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
|
||||
// THEN the entry properly records all extenders that returned true
|
||||
assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
|
||||
@@ -878,7 +888,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by a couple of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN one (but not all) of the extenders expires
|
||||
@@ -886,7 +896,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
|
||||
|
||||
// THEN the entry is not removed
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
|
||||
// THEN we don't re-query the extenders
|
||||
verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
|
||||
@@ -894,7 +904,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
|
||||
|
||||
// THEN the entry properly records all extenders that returned true
|
||||
assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
|
||||
assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -913,7 +923,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by a couple of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN all of the active extenders expire
|
||||
@@ -923,7 +933,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
|
||||
|
||||
// THEN the entry removed
|
||||
assertFalse(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertFalse(mCollection.getAllNotifs().contains(entry2));
|
||||
verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN);
|
||||
}
|
||||
|
||||
@@ -943,7 +953,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by a couple of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN the notification is reposted
|
||||
@@ -954,7 +964,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mExtender2).cancelLifetimeExtension(entry2);
|
||||
|
||||
// THEN the notification is still present
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@@ -973,7 +983,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by a couple of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
|
||||
@@ -1002,7 +1012,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// GIVEN a notification gets lifetime-extended by a couple of them
|
||||
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
|
||||
assertTrue(mCollection.getActiveNotifs().contains(entry2));
|
||||
assertTrue(mCollection.getAllNotifs().contains(entry2));
|
||||
clearInvocations(mExtender1, mExtender2, mExtender3);
|
||||
|
||||
// WHEN the notification is reposted
|
||||
@@ -1055,11 +1065,11 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// WHEN both notifications are manually dismissed together
|
||||
mCollection.dismissNotifications(
|
||||
List.of(new Pair(entry1, defaultStats(entry1)),
|
||||
new Pair(entry2, defaultStats(entry2))));
|
||||
List.of(new Pair<>(entry1, defaultStats(entry1)),
|
||||
new Pair<>(entry2, defaultStats(entry2))));
|
||||
|
||||
// THEN build list is only called one time
|
||||
verify(mBuildListener).onBuildList(any(Collection.class));
|
||||
verifyBuiltList(List.of(entry1, entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1074,8 +1084,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
DismissedByUserStats stats1 = defaultStats(entry1);
|
||||
DismissedByUserStats stats2 = defaultStats(entry2);
|
||||
mCollection.dismissNotifications(
|
||||
List.of(new Pair(entry1, defaultStats(entry1)),
|
||||
new Pair(entry2, defaultStats(entry2))));
|
||||
List.of(new Pair<>(entry1, defaultStats(entry1)),
|
||||
new Pair<>(entry2, defaultStats(entry2))));
|
||||
|
||||
// THEN we send the dismissals to system server
|
||||
verify(mStatusBarService).onNotificationClear(
|
||||
@@ -1109,8 +1119,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// WHEN both notifications are manually dismissed together
|
||||
mCollection.dismissNotifications(
|
||||
List.of(new Pair(entry1, defaultStats(entry1)),
|
||||
new Pair(entry2, defaultStats(entry2))));
|
||||
List.of(new Pair<>(entry1, defaultStats(entry1)),
|
||||
new Pair<>(entry2, defaultStats(entry2))));
|
||||
|
||||
// THEN the entries are marked as dismissed
|
||||
assertEquals(DISMISSED, entry1.getDismissState());
|
||||
@@ -1134,8 +1144,8 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
|
||||
// WHEN both notifications are manually dismissed together
|
||||
mCollection.dismissNotifications(
|
||||
List.of(new Pair(entry1, defaultStats(entry1)),
|
||||
new Pair(entry2, defaultStats(entry2))));
|
||||
List.of(new Pair<>(entry1, defaultStats(entry1)),
|
||||
new Pair<>(entry2, defaultStats(entry2))));
|
||||
|
||||
// THEN all interceptors get checked
|
||||
verify(mInterceptor1).shouldInterceptDismissal(entry1);
|
||||
@@ -1162,7 +1172,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
|
||||
|
||||
// THEN build list is only called one time
|
||||
verify(mBuildListener).onBuildList(any(Collection.class));
|
||||
verifyBuiltList(List.of(entry1, entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1271,13 +1281,18 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
|
||||
}
|
||||
|
||||
public CollectionEvent postNotif(NotificationEntryBuilder builder) {
|
||||
private CollectionEvent postNotif(NotificationEntryBuilder builder) {
|
||||
clearInvocations(mCollectionListener);
|
||||
NotifEvent rawEvent = mNoMan.postNotif(builder);
|
||||
verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
|
||||
return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
|
||||
}
|
||||
|
||||
private void verifyBuiltList(Collection<NotificationEntry> list) {
|
||||
verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
|
||||
assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
|
||||
}
|
||||
|
||||
private static class RecordingCollectionListener implements NotifCollectionListener {
|
||||
private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
|
||||
|
||||
@@ -1303,6 +1318,14 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
public void onEntryCleanUp(NotificationEntry entry) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRankingApplied() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRankingUpdate(RankingMap rankingMap) {
|
||||
}
|
||||
|
||||
public NotificationEntry getEntry(String key) {
|
||||
if (!mLastSeenEntries.containsKey(key)) {
|
||||
throw new RuntimeException("Key not found: " + key);
|
||||
|
||||
@@ -223,7 +223,7 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
|
||||
.setPkg(TEST_PKG)
|
||||
.setId(2)
|
||||
.build();
|
||||
when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
|
||||
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
|
||||
|
||||
// GIVEN that entry2 is currently associated with a foreground service
|
||||
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
|
||||
@@ -253,7 +253,7 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
|
||||
.setPkg(TEST_PKG)
|
||||
.setId(2)
|
||||
.build();
|
||||
when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry));
|
||||
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
|
||||
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
|
||||
.thenReturn(entry.getKey());
|
||||
|
||||
|
||||
@@ -329,10 +329,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testIconScrollXAfterTranslationAndReset() throws Exception {
|
||||
mGroupRow.setTranslation(50);
|
||||
assertEquals(50, -mGroupRow.getEntry().expandedIcon.getScrollX());
|
||||
assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
|
||||
|
||||
mGroupRow.resetTranslation();
|
||||
assertEquals(0, mGroupRow.getEntry().expandedIcon.getScrollX());
|
||||
assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.os.Handler;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.NotificationListenerService.Ranking;
|
||||
@@ -61,6 +62,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
|
||||
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
|
||||
import com.android.systemui.statusbar.notification.icon.IconBuilder;
|
||||
import com.android.systemui.statusbar.notification.icon.IconManager;
|
||||
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
|
||||
@@ -245,14 +248,13 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
|
||||
mLockscreenUserManager,
|
||||
pipeline,
|
||||
stage,
|
||||
true, /* allowLongPress */
|
||||
mock(KeyguardBypassController.class),
|
||||
mock(StatusBarStateController.class),
|
||||
mGroupManager,
|
||||
mGutsManager,
|
||||
mNotificationInterruptionStateProvider,
|
||||
RowInflaterTask::new,
|
||||
mExpandableNotificationRowComponentBuilder);
|
||||
mExpandableNotificationRowComponentBuilder,
|
||||
new IconManager(
|
||||
mEntryManager,
|
||||
mock(LauncherApps.class),
|
||||
new IconBuilder(mContext)));
|
||||
|
||||
mEntryManager.setUpWithPresenter(mPresenter);
|
||||
mEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.app.NotificationChannel;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
@@ -53,6 +54,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.icon.IconBuilder;
|
||||
import com.android.systemui.statusbar.notification.icon.IconManager;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
|
||||
@@ -93,6 +96,7 @@ public class NotificationTestHelper {
|
||||
private final NotifBindPipeline mBindPipeline;
|
||||
private final NotifCollectionListener mBindPipelineEntryListener;
|
||||
private final RowContentBindStage mBindStage;
|
||||
private final IconManager mIconManager;
|
||||
private StatusBarStateController mStatusBarStateController;
|
||||
|
||||
public NotificationTestHelper(Context context, TestableDependency dependency) {
|
||||
@@ -106,6 +110,10 @@ public class NotificationTestHelper {
|
||||
mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
|
||||
mock(ConfigurationControllerImpl.class));
|
||||
mGroupManager.setHeadsUpManager(mHeadsUpManager);
|
||||
mIconManager = new IconManager(
|
||||
mock(CommonNotifCollection.class),
|
||||
mock(LauncherApps.class),
|
||||
new IconBuilder(mContext));
|
||||
|
||||
NotificationContentInflater contentBinder = new NotificationContentInflater(
|
||||
mock(NotifRemoteViewCache.class),
|
||||
@@ -377,7 +385,7 @@ public class NotificationTestHelper {
|
||||
.build();
|
||||
|
||||
entry.setRow(row);
|
||||
entry.createIcons(mContext, entry.getSbn());
|
||||
mIconManager.createIcons(entry);
|
||||
row.setEntry(entry);
|
||||
|
||||
mBindPipelineEntryListener.onEntryInit(entry);
|
||||
|
||||
Reference in New Issue
Block a user