blob: a51896ee69fc2746bba8d0e9aec7b44125d506db [file] [log] [blame]
/*
* Copyright (C) 2008 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;
import static android.app.Notification.CATEGORY_ALARM;
import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
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 android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
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.Dependency;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* The list of currently displaying notifications.
*/
public class NotificationData {
private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
/**
* These dependencies are late init-ed
*/
private KeyguardEnvironment mEnvironment;
private NotificationMediaManager mMediaManager;
private HeadsUpManager mHeadsUpManager;
public static final class Entry {
private static final long LAUNCH_COOLDOWN = 2000;
private static final long REMOTE_INPUT_COOLDOWN = 500;
private static final long INITIALIZATION_DELAY = 400;
private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
private static final int COLOR_INVALID = 1;
public final String key;
public StatusBarNotification notification;
public NotificationChannel channel;
public long lastAudiblyAlertedMs;
public boolean noisy;
public boolean ambient;
public int importance;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
private boolean interruption;
public boolean autoRedacted; // whether the redacted notification was generated by us
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
/** Smart Actions provided by the NotificationAssistantService. */
@NonNull
public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
public CharSequence[] smartReplies = new CharSequence[0];
@VisibleForTesting
public int suppressedVisualEffects;
public boolean suspended;
private Entry parent; // our parent (if we're in a group)
private ArrayList<Entry> children = new ArrayList<Entry>();
private ExpandableNotificationRow row; // the outer expanded view
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
private InflationTask mRunningTask = null;
private Throwable mDebugThrowable;
public CharSequence remoteInputTextWhenReset;
public long lastRemoteInputSent = NOT_LAUNCHED_YET;
public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
public CharSequence headsUpStatusBarText;
public CharSequence headsUpStatusBarTextPublic;
private long initializationTime = -1;
/**
* Whether or not this row represents a system notification. Note that if this is
* {@code null}, that means we were either unable to retrieve the info or have yet to
* retrieve the info.
*/
public Boolean mIsSystemNotification;
/**
* Has the user sent a reply through this Notification.
*/
private boolean hasSentReply;
/**
* Whether this notification should be displayed as a bubble.
*/
private boolean mIsBubble;
/**
* Whether the user has dismissed this notification when it was in bubble form.
*/
private boolean mUserDismissedBubble;
public Entry(StatusBarNotification n) {
this(n, null);
}
public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
this.key = n.getKey();
this.notification = n;
if (ranking != null) {
populateFromRanking(ranking);
}
}
public void populateFromRanking(@NonNull Ranking ranking) {
channel = ranking.getChannel();
lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
importance = ranking.getImportance();
ambient = ranking.isAmbient();
snoozeCriteria = ranking.getSnoozeCriteria();
userSentiment = ranking.getUserSentiment();
systemGeneratedSmartActions = ranking.getSmartActions() == null
? Collections.emptyList() : ranking.getSmartActions();
smartReplies = ranking.getSmartReplies() == null
? new CharSequence[0]
: ranking.getSmartReplies().toArray(new CharSequence[0]);
suppressedVisualEffects = ranking.getSuppressedVisualEffects();
suspended = ranking.isSuspended();
}
public void setInterruption() {
interruption = true;
}
public boolean hasInterrupted() {
return interruption;
}
public void setIsBubble(boolean bubbleable) {
mIsBubble = bubbleable;
}
public boolean isBubble() {
return mIsBubble;
}
public void setBubbleDismissed(boolean userDismissed) {
mUserDismissedBubble = userDismissed;
}
public boolean isBubbleDismissed() {
return mUserDismissedBubble;
}
/**
* Resets the notification entry to be re-used.
*/
public void reset() {
if (row != null) {
row.reset();
}
}
public ExpandableNotificationRow getRow() {
return row;
}
//TODO: This will go away when we have a way to bind an entry to a row
public void setRow(ExpandableNotificationRow row) {
this.row = row;
}
@Nullable
public List<Entry> getChildren() {
if (children.size() <= 0) {
return null;
}
return children;
}
public void notifyFullScreenIntentLaunched() {
setInterruption();
lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
}
public boolean hasJustLaunchedFullScreenIntent() {
return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
}
public boolean hasJustSentRemoteInput() {
return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
}
public boolean hasFinishedInitialization() {
return initializationTime == -1 ||
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
*/
public void createIcons(Context context, StatusBarNotification sbn)
throws InflationException {
Notification n = sbn.getNotification();
final Icon smallIcon = n.getSmallIcon();
if (smallIcon == null) {
throw new InflationException("No small icon in notification from "
+ sbn.getPackageName());
}
// 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);
final StatusBarIcon ic = new StatusBarIcon(
sbn.getUser(),
sbn.getPackageName(),
smallIcon,
n.iconLevel,
n.number,
StatusBarIconView.contentDescForNotification(context, n));
if (!icon.set(ic) || !expandedIcon.set(ic)) {
icon = null;
expandedIcon = null;
throw new InflationException("Couldn't create icon: " + ic);
}
expandedIcon.setVisibility(View.INVISIBLE);
expandedIcon.setOnVisibilityChangedListener(
newVisibility -> {
if (row != null) {
row.setIconsVisible(newVisibility != View.VISIBLE);
}
});
}
public void setIconTag(int key, Object tag) {
if (icon != null) {
icon.setTag(key, tag);
expandedIcon.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
*/
public void updateIcons(Context context, StatusBarNotification sbn)
throws InflationException {
if (icon != null) {
// Update the icon
Notification n = sbn.getNotification();
final StatusBarIcon ic = new StatusBarIcon(
notification.getUser(),
notification.getPackageName(),
n.getSmallIcon(),
n.iconLevel,
n.number,
StatusBarIconView.contentDescForNotification(context, n));
icon.setNotification(sbn);
expandedIcon.setNotification(sbn);
if (!icon.set(ic) || !expandedIcon.set(ic)) {
throw new InflationException("Couldn't update icon: " + ic);
}
}
}
public int getContrastedColor(Context context, boolean isLowPriority,
int backgroundColor) {
int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
notification.getNotification().color;
if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
return mCachedContrastColor;
}
final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
backgroundColor);
mCachedContrastColorIsFor = rawColor;
mCachedContrastColor = contrasted;
return mCachedContrastColor;
}
/**
* Abort all existing inflation tasks
*/
public void abortTask() {
if (mRunningTask != null) {
mRunningTask.abort();
mRunningTask = null;
}
}
public void setInflationTask(InflationTask abortableTask) {
// abort any existing inflation
InflationTask existing = mRunningTask;
abortTask();
mRunningTask = abortableTask;
if (existing != null && mRunningTask != null) {
mRunningTask.supersedeTask(existing);
}
}
public void onInflationTaskFinished() {
mRunningTask = null;
}
@VisibleForTesting
public InflationTask getRunningTask() {
return mRunningTask;
}
/**
* Set a throwable that is used for debugging
*
* @param debugThrowable the throwable to save
*/
public void setDebugThrowable(Throwable debugThrowable) {
mDebugThrowable = debugThrowable;
}
public Throwable getDebugThrowable() {
return mDebugThrowable;
}
public void onRemoteInputInserted() {
lastRemoteInputSent = NOT_LAUNCHED_YET;
remoteInputTextWhenReset = null;
}
public void setHasSentReply() {
hasSentReply = true;
}
public boolean isLastMessageFromReply() {
if (!hasSentReply) {
return false;
}
Bundle extras = notification.getNotification().extras;
CharSequence[] replyTexts = extras.getCharSequenceArray(
Notification.EXTRA_REMOTE_INPUT_HISTORY);
if (!ArrayUtils.isEmpty(replyTexts)) {
return true;
}
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
if (messages != null && messages.length > 0) {
Parcelable message = messages[messages.length - 1];
if (message instanceof Bundle) {
Notification.MessagingStyle.Message lastMessage =
Notification.MessagingStyle.Message.getMessageFromBundle(
(Bundle) message);
if (lastMessage != null) {
Person senderPerson = lastMessage.getSenderPerson();
if (senderPerson == null) {
return true;
}
Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
return Objects.equals(user, senderPerson);
}
}
}
return false;
}
public void setInitializationTime(long time) {
if (initializationTime == -1) {
initializationTime = time;
}
}
public void sendAccessibilityEvent(int eventType) {
if (row != null) {
row.sendAccessibilityEvent(eventType);
}
}
/**
* Used by NotificationMediaManager to determine... things
* @return {@code true} if we are a media notification
*/
public boolean isMediaNotification() {
if (row == null) return false;
return row.isMediaRow();
}
/**
* We are a top level child if our parent is the list of notifications duh
* @return {@code true} if we're a top level notification
*/
public boolean isTopLevelChild() {
return row != null && row.isTopLevelChild();
}
public void resetUserExpansion() {
if (row != null) row.resetUserExpansion();
}
public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
if (row != null) row.freeContentViewWhenSafe(inflationFlag);
}
public void setAmbientPulsing(boolean pulsing) {
if (row != null) row.setAmbientPulsing(pulsing);
}
public boolean rowExists() {
return row != null;
}
public boolean isRowDismissed() {
return row != null && row.isDismissed();
}
public boolean isRowRemoved() {
return row != null && row.isRemoved();
}
/**
* @return {@code true} if the row is null or removed
*/
public boolean isRemoved() {
//TODO: recycling invalidates this
return row == null || row.isRemoved();
}
/**
* @return {@code true} if the row is null or dismissed
*/
public boolean isDismissed() {
//TODO: recycling
return row == null || row.isDismissed();
}
public boolean isRowPinned() {
return row != null && row.isPinned();
}
public void setRowPinned(boolean pinned) {
if (row != null) row.setPinned(pinned);
}
public boolean isRowAnimatingAway() {
return row != null && row.isHeadsUpAnimatingAway();
}
public boolean isRowHeadsUp() {
return row != null && row.isHeadsUp();
}
public void setHeadsUp(boolean shouldHeadsUp) {
if (row != null) row.setHeadsUp(shouldHeadsUp);
}
public boolean mustStayOnScreen() {
return row != null && row.mustStayOnScreen();
}
public void setHeadsUpIsVisible() {
if (row != null) row.setHeadsUpIsVisible();
}
//TODO: i'm imagining a world where this isn't just the row, but I could be rwong
public ExpandableNotificationRow getHeadsUpAnimationView() {
return row;
}
public void setUserLocked(boolean userLocked) {
if (row != null) row.setUserLocked(userLocked);
}
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
}
public void setGroupExpansionChanging(boolean changing) {
if (row != null) row.setGroupExpansionChanging(changing);
}
public void notifyHeightChanged(boolean needsAnimation) {
if (row != null) row.notifyHeightChanged(needsAnimation);
}
public void closeRemoteInput() {
if (row != null) row.closeRemoteInput();
}
public boolean areChildrenExpanded() {
return row != null && row.areChildrenExpanded();
}
public boolean keepInParent() {
return row != null && row.keepInParent();
}
//TODO: probably less confusing to say "is group fully visible"
public boolean isGroupNotFullyVisible() {
return row == null || row.isGroupNotFullyVisible();
}
public NotificationGuts getGuts() {
if (row != null) return row.getGuts();
return null;
}
public boolean hasLowPriorityStateUpdated() {
return row != null && row.hasLowPriorityStateUpdated();
}
public void removeRow() {
if (row != null) row.setRemoved();
}
public boolean isSummaryWithChildren() {
return row != null && row.isSummaryWithChildren();
}
public void setKeepInParent(boolean keep) {
if (row != null) row.setKeepInParent(keep);
}
public void onDensityOrFontScaleChanged() {
if (row != null) row.onDensityOrFontScaleChanged();
}
public boolean areGutsExposed() {
return row != null && row.getGuts() != null && row.getGuts().isExposed();
}
public boolean isChildInGroup() {
return parent == null;
}
public void setLowPriorityStateUpdated(boolean updated) {
if (row != null) row.setLowPriorityStateUpdated(updated);
}
/**
* @return Can the underlying notification be cleared? This can be different from whether the
* notification can be dismissed in case notifications are sensitive on the lockscreen.
* @see #canViewBeDismissed()
*/
public boolean isClearable() {
if (notification == null || !notification.isClearable()) {
return false;
}
if (children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Entry child = children.get(i);
if (!child.isClearable()) {
return false;
}
}
}
return true;
}
public boolean canViewBeDismissed() {
if (row == null) return true;
return row.canViewBeDismissed();
}
boolean isExemptFromDndVisualSuppression() {
if (isNotificationBlockedByPolicy(notification.getNotification())) {
return false;
}
if ((notification.getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0) {
return true;
}
if (notification.getNotification().isMediaNotification()) {
return true;
}
if (mIsSystemNotification != null && mIsSystemNotification) {
return true;
}
return false;
}
private boolean shouldSuppressVisualEffect(int effect) {
if (isExemptFromDndVisualSuppression()) {
return false;
}
return (suppressedVisualEffects & effect) != 0;
}
/**
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
* is set for this entry.
*/
public boolean shouldSuppressFullScreenIntent() {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
}
/**
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}
* is set for this entry.
*/
public boolean shouldSuppressPeek() {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
}
/**
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR}
* is set for this entry.
*/
public boolean shouldSuppressStatusBar() {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
}
/**
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}
* is set for this entry.
*/
public boolean shouldSuppressAmbient() {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
}
/**
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
* is set for this entry.
*/
public boolean shouldSuppressNotificationList() {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
}
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
private final ArrayList<Entry> mFilteredForUser = new ArrayList<>();
private final NotificationGroupManager mGroupManager
= Dependency.get(NotificationGroupManager.class);
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
private final Ranking mRankingA = new Ranking();
private final Ranking mRankingB = new Ranking();
@Override
public int compare(Entry a, Entry b) {
final StatusBarNotification na = a.notification;
final StatusBarNotification nb = b.notification;
int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
int aRank = 0;
int bRank = 0;
if (mRankingMap != null) {
// RankingMap as received from NoMan
getRanking(a.key, mRankingA);
getRanking(b.key, mRankingB);
aImportance = mRankingA.getImportance();
bImportance = mRankingB.getImportance();
aRank = mRankingA.getRank();
bRank = mRankingB.getRank();
}
String mediaNotification = getMediaManager().getMediaNotificationKey();
// IMPORTANCE_MIN media streams are allowed to drift to the bottom
final boolean aMedia = a.key.equals(mediaNotification)
&& aImportance > NotificationManager.IMPORTANCE_MIN;
final boolean bMedia = b.key.equals(mediaNotification)
&& bImportance > NotificationManager.IMPORTANCE_MIN;
boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH &&
isSystemNotification(na);
boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH &&
isSystemNotification(nb);
boolean isHeadsUp = a.row.isHeadsUp();
if (isHeadsUp != b.row.isHeadsUp()) {
return isHeadsUp ? -1 : 1;
} else if (isHeadsUp) {
// Provide consistent ranking with headsUpManager
return mHeadsUpManager.compare(a, b);
} else if (a.row.isAmbientPulsing() != b.row.isAmbientPulsing()) {
return a.row.isAmbientPulsing() ? -1 : 1;
} else if (aMedia != bMedia) {
// Upsort current media notification.
return aMedia ? -1 : 1;
} else if (aSystemMax != bSystemMax) {
// Upsort PRIORITY_MAX system notifications
return aSystemMax ? -1 : 1;
} else if (aRank != bRank) {
return aRank - bRank;
} else {
return Long.compare(nb.getNotification().when, na.getNotification().when);
}
}
};
private KeyguardEnvironment getEnvironment() {
if (mEnvironment == null) {
mEnvironment = Dependency.get(KeyguardEnvironment.class);
}
return mEnvironment;
}
private NotificationMediaManager getMediaManager() {
if (mMediaManager == null) {
mMediaManager = Dependency.get(NotificationMediaManager.class);
}
return mMediaManager;
}
/**
* Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
*
* <p>
* This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
* when the environment changes.
* <p>
* Don't hold on to or modify the returned list.
*/
public ArrayList<Entry> getActiveNotifications() {
return mSortedAndFiltered;
}
public ArrayList<Entry> getNotificationsForCurrentUser() {
mFilteredForUser.clear();
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
final StatusBarNotification sbn = entry.notification;
if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
continue;
}
mFilteredForUser.add(entry);
}
}
return mFilteredForUser;
}
public Entry get(String key) {
return mEntries.get(key);
}
public void add(Entry entry) {
synchronized (mEntries) {
mEntries.put(entry.notification.getKey(), entry);
}
mGroupManager.onEntryAdded(entry);
updateRankingAndSort(mRankingMap);
}
public Entry remove(String key, RankingMap ranking) {
Entry removed;
synchronized (mEntries) {
removed = mEntries.remove(key);
}
if (removed == null) return null;
mGroupManager.onEntryRemoved(removed);
updateRankingAndSort(ranking);
return removed;
}
/** Updates the given notification entry with the provided ranking. */
public void update(Entry entry, RankingMap ranking, StatusBarNotification notification) {
updateRanking(ranking);
final StatusBarNotification oldNotification = entry.notification;
entry.notification = notification;
mGroupManager.onEntryUpdated(entry, oldNotification);
}
public void updateRanking(RankingMap ranking) {
updateRankingAndSort(ranking);
}
public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
if (uid == entry.notification.getUid()
&& pkg.equals(entry.notification.getPackageName())
&& key.equals(entry.key)) {
if (showIcon) {
entry.mActiveAppOps.add(appOp);
} else {
entry.mActiveAppOps.remove(appOp);
}
}
}
}
}
/**
* Returns true if this notification should be displayed in the high-priority notifications
* section (and on the lockscreen and status bar).
*/
public boolean isHighPriority(StatusBarNotification statusBarNotification) {
if (mRankingMap != null) {
getRanking(statusBarNotification.getKey(), mTmpRanking);
if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
|| statusBarNotification.getNotification().isForegroundService()
|| statusBarNotification.getNotification().hasMediaSession()) {
return true;
}
if (mGroupManager.isSummaryOfGroup(statusBarNotification)) {
for (Entry child : mGroupManager.getLogicalChildren(statusBarNotification)) {
if (isHighPriority(child.notification)) {
return true;
}
}
}
}
return false;
}
public boolean isAmbient(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.isAmbient();
}
return false;
}
public int getVisibilityOverride(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getVisibilityOverride();
}
return Ranking.VISIBILITY_NO_OVERRIDE;
}
/**
* Categories that are explicitly called out on DND settings screens are always blocked, if
* DND has flagged them, even if they are foreground or system notifications that might
* otherwise visually bypass DND.
*/
private static boolean isNotificationBlockedByPolicy(Notification n) {
if (isCategory(CATEGORY_CALL, n)
|| isCategory(CATEGORY_MESSAGE, n)
|| isCategory(CATEGORY_ALARM, n)
|| isCategory(CATEGORY_EVENT, n)
|| isCategory(CATEGORY_REMINDER, n)) {
return true;
}
return false;
}
private static boolean isCategory(String category, Notification n) {
return Objects.equals(n.category, category);
}
public int getImportance(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getImportance();
}
return NotificationManager.IMPORTANCE_UNSPECIFIED;
}
public String getOverrideGroupKey(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getOverrideGroupKey();
}
return null;
}
public List<SnoozeCriterion> getSnoozeCriteria(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getSnoozeCriteria();
}
return null;
}
public NotificationChannel getChannel(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getChannel();
}
return null;
}
public int getRank(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.getRank();
}
return 0;
}
public boolean shouldHide(String key) {
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
return mTmpRanking.isSuspended();
}
return false;
}
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
if (!getRanking(entry.key, mTmpRanking)) {
continue;
}
final StatusBarNotification oldSbn = entry.notification.cloneLight();
final String overrideGroupKey = getOverrideGroupKey(entry.key);
if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
entry.populateFromRanking(mTmpRanking);
}
}
}
filterAndSort();
}
/**
* Get the ranking from the current ranking map.
*
* @param key the key to look up
* @param outRanking the ranking to populate
*
* @return {@code true} if the ranking was properly obtained.
*/
@VisibleForTesting
protected boolean getRanking(String key, Ranking outRanking) {
return mRankingMap.getRanking(key, outRanking);
}
// TODO: This should not be public. Instead the Environment should notify this class when
// anything changed, and this class should call back the UI so it updates itself.
public void filterAndSort() {
mSortedAndFiltered.clear();
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
if (mNotificationFilter.shouldFilterOut(entry)) {
continue;
}
mSortedAndFiltered.add(entry);
}
}
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
public void dump(PrintWriter pw, String indent) {
int N = mSortedAndFiltered.size();
pw.print(indent);
pw.println("active notifications: " + N);
int active;
for (active = 0; active < N; active++) {
NotificationData.Entry e = mSortedAndFiltered.get(active);
dumpEntry(pw, indent, active, e);
}
synchronized (mEntries) {
int M = mEntries.size();
pw.print(indent);
pw.println("inactive notifications: " + (M - active));
int inactiveCount = 0;
for (int i = 0; i < M; i++) {
Entry entry = mEntries.valueAt(i);
if (!mSortedAndFiltered.contains(entry)) {
dumpEntry(pw, indent, inactiveCount, entry);
inactiveCount++;
}
}
}
}
private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
getRanking(e.key, mTmpRanking);
pw.print(indent);
pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
StatusBarNotification n = e.notification;
pw.print(indent);
pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" +
mTmpRanking.getImportance());
pw.print(indent);
pw.println(" notification=" + n.getNotification());
}
private static boolean isSystemNotification(StatusBarNotification sbn) {
String sbnPackage = sbn.getPackageName();
return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
}
/**
* Provides access to keyguard state and user settings dependent data.
*/
public interface KeyguardEnvironment {
boolean isDeviceProvisioned();
boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
}
}