blob: 5c43f8f829b0ec08bfedd75ac5a7414f3284f1f9 [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 android.service.notification;
import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.ArrayList;
/**
* Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
* the status bar and any {@link android.service.notification.NotificationListenerService}s.
*/
public class StatusBarNotification implements Parcelable {
static final int MAX_LOG_TAG_LENGTH = 36;
@UnsupportedAppUsage
private final String pkg;
@UnsupportedAppUsage
private final int id;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final String tag;
private final String key;
private String groupKey;
private String overrideGroupKey;
@UnsupportedAppUsage
private final int uid;
private final String opPkg;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final int initialPid;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final Notification notification;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final UserHandle user;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final long postTime;
// A small per-notification ID, used for statsd logging.
private InstanceId mInstanceId; // Not final, see setInstanceId()
private Context mContext; // used for inflation & icon expansion
/** @hide */
public StatusBarNotification(String pkg, String opPkg, int id,
String tag, int uid, int initialPid, Notification notification, UserHandle user,
String overrideGroupKey, long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
this.pkg = pkg;
this.opPkg = opPkg;
this.id = id;
this.tag = tag;
this.uid = uid;
this.initialPid = initialPid;
this.notification = notification;
this.user = user;
this.postTime = postTime;
this.overrideGroupKey = overrideGroupKey;
this.key = key();
this.groupKey = groupKey();
}
/**
* @deprecated Non-system apps should not need to create StatusBarNotifications.
*/
@Deprecated
public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid,
int initialPid, int score, Notification notification, UserHandle user,
long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
this.pkg = pkg;
this.opPkg = opPkg;
this.id = id;
this.tag = tag;
this.uid = uid;
this.initialPid = initialPid;
this.notification = notification;
this.user = user;
this.postTime = postTime;
this.key = key();
this.groupKey = groupKey();
}
public StatusBarNotification(Parcel in) {
this.pkg = in.readString();
this.opPkg = in.readString();
this.id = in.readInt();
if (in.readInt() != 0) {
this.tag = in.readString();
} else {
this.tag = null;
}
this.uid = in.readInt();
this.initialPid = in.readInt();
this.notification = new Notification(in);
this.user = UserHandle.readFromParcel(in);
this.postTime = in.readLong();
if (in.readInt() != 0) {
this.overrideGroupKey = in.readString();
}
if (in.readInt() != 0) {
this.mInstanceId = InstanceId.CREATOR.createFromParcel(in);
}
this.key = key();
this.groupKey = groupKey();
}
private String key() {
String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
if (overrideGroupKey != null && getNotification().isGroupSummary()) {
sbnKey = sbnKey + "|" + overrideGroupKey;
}
return sbnKey;
}
private String groupKey() {
if (overrideGroupKey != null) {
return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
}
final String group = getNotification().getGroup();
final String sortKey = getNotification().getSortKey();
if (group == null && sortKey == null) {
// a group of one
return key;
}
return user.getIdentifier() + "|" + pkg + "|" +
(group == null
? "c:" + notification.getChannelId()
: "g:" + group);
}
/**
* Returns true if this notification is part of a group.
*/
public boolean isGroup() {
if (overrideGroupKey != null || isAppGroup()) {
return true;
}
return false;
}
/**
* Returns true if application asked that this notification be part of a group.
*/
public boolean isAppGroup() {
if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
return true;
}
return false;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(this.pkg);
out.writeString(this.opPkg);
out.writeInt(this.id);
if (this.tag != null) {
out.writeInt(1);
out.writeString(this.tag);
} else {
out.writeInt(0);
}
out.writeInt(this.uid);
out.writeInt(this.initialPid);
this.notification.writeToParcel(out, flags);
user.writeToParcel(out, flags);
out.writeLong(this.postTime);
if (this.overrideGroupKey != null) {
out.writeInt(1);
out.writeString(this.overrideGroupKey);
} else {
out.writeInt(0);
}
if (this.mInstanceId != null) {
out.writeInt(1);
mInstanceId.writeToParcel(out, flags);
} else {
out.writeInt(0);
}
}
public int describeContents() {
return 0;
}
public static final @android.annotation.NonNull
Parcelable.Creator<StatusBarNotification> CREATOR =
new Parcelable.Creator<StatusBarNotification>() {
public StatusBarNotification createFromParcel(Parcel parcel) {
return new StatusBarNotification(parcel);
}
public StatusBarNotification[] newArray(int size) {
return new StatusBarNotification[size];
}
};
/**
* @hide
*/
public StatusBarNotification cloneLight() {
final Notification no = new Notification();
this.notification.cloneInto(no, false); // light copy
return cloneShallow(no);
}
@Override
public StatusBarNotification clone() {
return cloneShallow(this.notification.clone());
}
/**
* @param notification Some kind of clone of this.notification.
* @return A shallow copy of self, with notification in place of this.notification.
*/
StatusBarNotification cloneShallow(Notification notification) {
StatusBarNotification result = new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
notification, this.user, this.overrideGroupKey, this.postTime);
result.setInstanceId(this.mInstanceId);
return result;
}
@Override
public String toString() {
return String.format(
"StatusBarNotification(pkg=%s user=%s id=%d tag=%s key=%s: %s)",
this.pkg, this.user, this.id, this.tag,
this.key, this.notification);
}
/**
* Convenience method to check the notification's flags for
* {@link Notification#FLAG_ONGOING_EVENT}.
*/
public boolean isOngoing() {
return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
}
/**
* Convenience method to check the notification's flags for
* either {@link Notification#FLAG_ONGOING_EVENT} or
* {@link Notification#FLAG_NO_CLEAR}.
*/
public boolean isClearable() {
return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
&& ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
}
/**
* Returns a userid for whom this notification is intended.
*
* @deprecated Use {@link #getUser()} instead.
*/
@Deprecated
public int getUserId() {
return this.user.getIdentifier();
}
/**
* Like {@link #getUserId()} but handles special users.
* @hide
*/
public int getNormalizedUserId() {
int userId = getUserId();
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
}
return userId;
}
/** The package that the notification belongs to. */
public String getPackageName() {
return pkg;
}
/** The id supplied to {@link android.app.NotificationManager#notify(int, Notification)}. */
public int getId() {
return id;
}
/**
* The tag supplied to {@link android.app.NotificationManager#notify(int, Notification)},
* or null if no tag was specified.
*/
public String getTag() {
return tag;
}
/**
* The notifying app's ({@link #getPackageName()}'s) uid.
*/
public int getUid() {
return uid;
}
/**
* The package that posted the notification.
* <p> Might be different from {@link #getPackageName()} if the app owning the notification has
* a {@link NotificationManager#setNotificationDelegate(String) notification delegate}.
*/
public @NonNull String getOpPkg() {
return opPkg;
}
/** @hide */
@UnsupportedAppUsage
public int getInitialPid() {
return initialPid;
}
/**
* The {@link android.app.Notification} supplied to
* {@link android.app.NotificationManager#notify(int, Notification)}.
*/
public Notification getNotification() {
return notification;
}
/**
* The {@link android.os.UserHandle} for whom this notification is intended.
*/
public UserHandle getUser() {
return user;
}
/**
* The time (in {@link System#currentTimeMillis} time) the notification was posted,
* which may be different than {@link android.app.Notification#when}.
*/
public long getPostTime() {
return postTime;
}
/**
* A unique instance key for this notification record.
*/
public String getKey() {
return key;
}
/**
* A key that indicates the group with which this message ranks.
*/
public String getGroupKey() {
return groupKey;
}
/**
* The ID passed to setGroup(), or the override, or null.
*
* @hide
*/
public String getGroup() {
if (overrideGroupKey != null) {
return overrideGroupKey;
}
return getNotification().getGroup();
}
/**
* Sets the override group key.
*/
public void setOverrideGroupKey(String overrideGroupKey) {
this.overrideGroupKey = overrideGroupKey;
groupKey = groupKey();
}
/**
* Returns the override group key.
*/
public String getOverrideGroupKey() {
return overrideGroupKey;
}
/**
* @hide
*/
public void clearPackageContext() {
mContext = null;
}
/**
* @hide
*/
public InstanceId getInstanceId() {
return mInstanceId;
}
/**
* @hide
*/
public void setInstanceId(InstanceId instanceId) {
mInstanceId = instanceId;
}
/**
* @hide
*/
@UnsupportedAppUsage
public Context getPackageContext(Context context) {
if (mContext == null) {
try {
ApplicationInfo ai = context.getPackageManager()
.getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
getUserId());
mContext = context.createApplicationContext(ai,
Context.CONTEXT_RESTRICTED);
} catch (PackageManager.NameNotFoundException e) {
mContext = null;
}
}
if (mContext == null) {
mContext = context;
}
return mContext;
}
/**
* Returns a LogMaker that contains all basic information of the notification.
*
* @hide
*/
public LogMaker getLogMaker() {
LogMaker logMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN).setPackageName(getPackageName())
.addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
.addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag())
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
getNotification().isGroupSummary() ? 1 : 0)
.addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CATEGORY,
getNotification().category);
if (getNotification().extras != null) {
// Log the style used, if present. We only log the hash here, as notification log
// events are frequent, while there are few styles (hence low chance of collisions).
String template = getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
if (template != null && !template.isEmpty()) {
logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE,
template.hashCode());
}
ArrayList<Person> people = getNotification().extras.getParcelableArrayList(
Notification.EXTRA_PEOPLE_LIST);
if (people != null && !people.isEmpty()) {
logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_PEOPLE, people.size());
}
}
return logMaker;
}
/**
* @hide
*/
public String getShortcutId(Context context) {
String conversationId = getNotification().getShortcutId();
if (TextUtils.isEmpty(conversationId)
&& (Settings.Global.getInt(context.getContentResolver(),
Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 0)
&& getNotification().getNotificationStyle() == Notification.MessagingStyle.class) {
conversationId = getId() + getTag() + PLACEHOLDER_CONVERSATION_ID;
}
return conversationId;
}
/**
* Returns a probably-unique string based on the notification's group name,
* with no more than MAX_LOG_TAG_LENGTH characters.
* @return String based on group name of notification.
* @hide
*/
public String getGroupLogTag() {
return shortenTag(getGroup());
}
/**
* Returns a probably-unique string based on the notification's channel ID,
* with no more than MAX_LOG_TAG_LENGTH characters.
* @return String based on channel ID of notification.
* @hide
*/
public String getChannelIdLogTag() {
if (notification.getChannelId() == null) {
return null;
}
return shortenTag(notification.getChannelId());
}
// Make logTag with max size MAX_LOG_TAG_LENGTH.
// For shorter or equal tags, returns the tag.
// For longer tags, truncate the tag and append a hash of the full tag to
// fill the maximum size.
private String shortenTag(String logTag) {
if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
return logTag;
}
String hash = Integer.toHexString(logTag.hashCode());
return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
+ hash;
}
}