/* * Copyright (C) 2014 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.server.notification; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MIN; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX; import android.app.Notification; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.EventLogTags; import java.io.PrintWriter; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Objects; /** * Holds data about notifications that should not be shared with the * {@link android.service.notification.NotificationListenerService}s. * *
These objects should not be mutated unless the code is synchronized * on {@link NotificationManagerService#mNotificationList}, and any * modification should be followed by a sorting of that list.
* *Is sortable by {@link NotificationComparator}.
* * {@hide} */ public final class NotificationRecord { static final String TAG = "NotificationRecord"; static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); final StatusBarNotification sbn; final int mOriginalFlags; private final Context mContext; NotificationUsageStats.SingleNotificationStats stats; boolean isCanceled; /** Whether the notification was seen by the user via one of the notification listeners. */ boolean mIsSeen; // These members are used by NotificationSignalExtractors // to communicate with the ranking module. private float mContactAffinity; private boolean mRecentlyIntrusive; // is this notification currently being intercepted by Zen Mode? private boolean mIntercept; // The timestamp used for ranking. private long mRankingTimeMs; // The first post time, stable across updates. private long mCreationTimeMs; // The most recent visibility event. private long mVisibleSinceMs; // The most recent update time, or the creation time if no updates. private long mUpdateTimeMs; // Is this record an update of an old record? public boolean isUpdate; private int mPackagePriority; private int mAuthoritativeRank; private String mGlobalSortKey; private int mPackageVisibility; private int mUserImportance = IMPORTANCE_UNSPECIFIED; private int mImportance = IMPORTANCE_UNSPECIFIED; private CharSequence mImportanceExplanation = null; private int mSuppressedVisualEffects = 0; private String mUserExplanation; private String mPeopleExplanation; @VisibleForTesting public NotificationRecord(Context context, StatusBarNotification sbn) { this.sbn = sbn; mOriginalFlags = sbn.getNotification().flags; mRankingTimeMs = calculateRankingTimeMs(0L); mCreationTimeMs = sbn.getPostTime(); mUpdateTimeMs = mCreationTimeMs; mContext = context; stats = new NotificationUsageStats.SingleNotificationStats(); mImportance = defaultImportance(); } private int defaultImportance() { final Notification n = sbn.getNotification(); int importance = IMPORTANCE_DEFAULT; // Migrate notification flags to scores if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) { n.priority = Notification.PRIORITY_MAX; } switch (n.priority) { case Notification.PRIORITY_MIN: importance = IMPORTANCE_MIN; break; case Notification.PRIORITY_LOW: importance = IMPORTANCE_LOW; break; case Notification.PRIORITY_DEFAULT: importance = IMPORTANCE_DEFAULT; break; case Notification.PRIORITY_HIGH: importance = IMPORTANCE_HIGH; break; case Notification.PRIORITY_MAX: importance = IMPORTANCE_MAX; break; } stats.requestedImportance = importance; boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0 || (n.defaults & Notification.DEFAULT_VIBRATE) != 0 || n.sound != null || n.vibrate != null; stats.isNoisy = isNoisy; if (!isNoisy && importance > IMPORTANCE_LOW) { importance = IMPORTANCE_LOW; } if (isNoisy) { if (importance < IMPORTANCE_DEFAULT) { importance = IMPORTANCE_DEFAULT; } } if (n.fullScreenIntent != null) { importance = IMPORTANCE_MAX; } stats.naturalImportance = importance; return importance; } // copy any notes that the ranking system may have made before the update public void copyRankingInformation(NotificationRecord previous) { mContactAffinity = previous.mContactAffinity; mRecentlyIntrusive = previous.mRecentlyIntrusive; mPackagePriority = previous.mPackagePriority; mPackageVisibility = previous.mPackageVisibility; mIntercept = previous.mIntercept; mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); mCreationTimeMs = previous.mCreationTimeMs; mVisibleSinceMs = previous.mVisibleSinceMs; if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) { sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey()); } // Don't copy importance information or mGlobalSortKey, recompute them. } public Notification getNotification() { return sbn.getNotification(); } public int getFlags() { return sbn.getNotification().flags; } public UserHandle getUser() { return sbn.getUser(); } public String getKey() { return sbn.getKey(); } /** @deprecated Use {@link #getUser()} instead. */ public int getUserId() { return sbn.getUserId(); } void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) { final Notification notification = sbn.getNotification(); final Icon icon = notification.getSmallIcon(); String iconStr = String.valueOf(icon); if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId()); } pw.println(prefix + this); pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); pw.println(prefix + " icon=" + iconStr); pw.println(prefix + " pri=" + notification.priority); pw.println(prefix + " key=" + sbn.getKey()); pw.println(prefix + " seen=" + mIsSeen); pw.println(prefix + " groupKey=" + getGroupKey()); pw.println(prefix + " contentIntent=" + notification.contentIntent); pw.println(prefix + " deleteIntent=" + notification.deleteIntent); pw.println(prefix + " tickerText=" + notification.tickerText); pw.println(prefix + " contentView=" + notification.contentView); pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x", notification.defaults, notification.flags)); pw.println(prefix + " sound=" + notification.sound); pw.println(prefix + " audioStreamType=" + notification.audioStreamType); pw.println(prefix + " audioAttributes=" + notification.audioAttributes); pw.println(prefix + String.format(" color=0x%08x", notification.color)); pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); if (notification.actions != null && notification.actions.length > 0) { pw.println(prefix + " actions={"); final int N = notification.actions.length; for (int i=0; i