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:
Ned Burns
2020-03-17 01:48:25 +00:00
committed by Android (Google) Code Review
26 changed files with 884 additions and 470 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -269,7 +269,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
}
updateIsolatedIconLocation(false /* requireUpdate */);
mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
: newEntry.icon, animateIsolation);
: newEntry.getIcons().getStatusBarIcon(), animateIsolation);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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